feat(core): traceroute and tcpdump after Java, measured against Java

TerminalRun adds nine stories: traceroute in the LAN, through the router,
unreachable, without a route, to no one, to the host itself, errors and
Ctrl+C; tcpdump on two hosts. traceroute counts failures across hops as
Java does and takes an error that carries its probe; tcpdump tells each
frame's highest capture row with its time of day, at once.

Departures from Java: traceroute tells its summary before the end mark
(Java after it, where its window shows no text) and calls a host at the
20th hop reached. arpsend is not built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 10:46:18 +00:00
co-authored by Claude Opus 5.5
parent 495c449ef2
commit a867c33d64
16 changed files with 11983 additions and 16 deletions
+3 -3
View File
File diff suppressed because one or more lines are too long
+140 -4
View File
@@ -2,7 +2,7 @@
// handling of GUIApplicationTerminalWindow): a line's first word is the // handling of GUIApplicationTerminalWindow): a line's first word is the
// command, the others its parameters; the program tells its window texts, // command, the others its parameters; the program tells its window texts,
// in the project's language, as Java writes them, and marks a command that // in the project's language, as Java writes them, and marks a command that
// runs on (ping, host, nslookup) busy and done. The files are the node's // runs on (ping, host, nslookup, traceroute, tcpdump) busy and done. The files are the node's
// (the snapshot's file system, the same the project keeps); the network // (the snapshot's file system, the same the project keeps); the network
// commands ask the engine (ARP table, sockets, DNS, ICMP echo). // commands ask the engine (ARP table, sockets, DNS, ICMP echo).
// //
@@ -13,7 +13,12 @@
// - arp and netstat list their rows in the order learned or made (Java: a // - arp and netstat list their rows in the order learned or made (Java: a
// HashMap's order); netstat lists a UDP server's listening socket only // HashMap's order); netstat lists a UDP server's listening socket only
// (Java also a socket for each address and port it ever answered, never // (Java also a socket for each address and port it ever answered, never
// closed). // closed);
// - traceroute tells its summary before the end mark (Java after it, where
// its window shows no more text), and a host reached at the 20th hop is
// reached (Java: not reached);
// - tcpdump tells each frame at once (Java looks every 100 ms).
// arpsend is not built: the command is unknown here.
// //
// Departures from Java on the files (terminal.test.ts names each): // Departures from Java on the files (terminal.test.ts names each):
// - a text file's size is its length in characters (Java decodes the text as // - a text file's size is its length in characters (Java decodes the text as
@@ -37,9 +42,10 @@ import type { Id } from '../../ids.js';
import type { VirtualFileContent, VirtualFileSystem } from '../../model/virtualFileSystem.js'; import type { VirtualFileContent, VirtualFileSystem } from '../../model/virtualFileSystem.js';
import { entryName, parentPath } from '../../model/virtualFileSystem.js'; import { entryName, parentPath } from '../../model/virtualFileSystem.js';
import { javaIpCheck } from '../../util/javaIp.js'; import { javaIpCheck } from '../../util/javaIp.js';
import { isBroadcastFrom, type NetworkRuntimeEngine, type SocketRecord } from '../engine.js'; import { captureCommentText, captureRows, ICMP_TYPE_CODE, type CaptureCommentWords } from '../captureRows.js';
import { isBroadcastFrom, type IcmpObservation, type NetworkRuntimeEngine, type SocketRecord } from '../engine.js';
import { javaTrim } from '../http.js'; import { javaTrim } from '../http.js';
import { pause, type Steps } from '../threads.js'; import { pause, waitUntil, type Steps } from '../threads.js';
import { javaBase64 } from '../../util/javaBase64.js'; import { javaBase64 } from '../../util/javaBase64.js';
import { CommandThread, ProgramLog } from './program.js'; import { CommandThread, ProgramLog } from './program.js';
import type { PageLanguage } from './routerPages.js'; import type { PageLanguage } from './routerPages.js';
@@ -122,11 +128,39 @@ const COMMANDS = new Set([
'pwd', 'pwd',
'rm', 'rm',
'route', 'route',
'tcpdump',
'test', 'test',
'touch', 'touch',
'traceroute',
'type', 'type',
]); ]);
/** Terminal.traceroute: at most this many hops. */
const TRACEROUTE_MAXIMUM_HOPS = 20;
/** Terminal.traceroute: the first probe's number less one (42 * 23). */
const TRACEROUTE_FIRST_SEQUENCE_NUMBER = 42 * 23;
/** Lauscher's words for the capture's comments (rp_lauscher_msg12–14), and its numbers: the locale's grouping. */
const CAPTURE_WORDS: Record<PageLanguage, CaptureCommentWords> = {
de: { protocol: 'Protokoll', searchForMac: 'Suche nach MAC f\u00fcr', macIs: 'MAC ist' },
en: { protocol: 'Protocol', searchForMac: 'Search for MAC', macIs: 'MAC is' },
};
const CAPTURE_GROUPING: Record<PageLanguage, string> = { de: '.', en: ',' };
/** Lauscher.formatTimestamp: the time of day, HH:mm:ss.SSS. */
function timeOfDay(milliseconds: number): string {
const time = new Date(milliseconds);
const two = (value: number) => String(value).padStart(2, '0');
return `${two(time.getHours())}:${two(time.getMinutes())}:${two(time.getSeconds())}.${String(time.getMilliseconds()).padStart(3, '0')}`;
}
/** String.format with %s only: each in turn takes the next value. */
function formatted(template: string, ...values: readonly (string | number)[]): string {
let index = 0;
return template.replace(/%s/g, () => String(values[index++]));
}
/** Information.getPingRepeat: the echoes a ping sends. */ /** Information.getPingRepeat: the echoes a ping sends. */
const PING_REPEAT = 4; const PING_REPEAT = 4;
@@ -200,6 +234,7 @@ export class Terminal {
/** The window's Ctrl+C (Terminal.setInterrupt). */ /** The window's Ctrl+C (Terminal.setInterrupt). */
interrupt(): void { interrupt(): void {
this.interrupted = true; this.interrupted = true;
this.engine.resumeThreads();
} }
private tell(text: string): void { private tell(text: string): void {
@@ -236,6 +271,8 @@ export class Terminal {
host: (each) => this.host(each), host: (each) => this.host(each),
nslookup: (each) => this.nslookup(each), nslookup: (each) => this.nslookup(each),
ping: (each) => this.ping(each), ping: (each) => this.ping(each),
tcpdump: () => this.tcpdump(),
traceroute: (each) => this.traceroute(each),
}; };
if (Object.hasOwn(network, command)) { if (Object.hasOwn(network, command)) {
yield* network[command]!(parameters); yield* network[command]!(parameters);
@@ -586,4 +623,103 @@ export class Terminal {
} }
this.tell(text); this.tell(text);
} }
/**
* Terminal.traceroute: echo requests with TTL 1, 2, … (three tries a hop),
* each waiting for the first ICMP message back; a hop answers with time
* exceeded, the host with its echo reply, a router that cannot go on with
* unreachable. The summary comes before the end mark (Java's after it,
* where its window no longer shows text).
*/
private *traceroute(parameters: readonly string[]): Steps {
if (parameters.length !== 1) return this.usage('traceroute');
const target = parameters[0]!;
let address = javaIpCheck(target) ?? undefined;
if (address === undefined) {
const resolution = yield* this.engine.resolveDnsAddressSteps(this.nodeId, target);
if (resolution.type === 'timedOut') return this.tell(this.text('sw_terminal_trace_msg2'));
address = resolution.address;
}
if (address === undefined) return this.tell(this.text('sw_terminal_trace_msg3'));
this.busy(true);
this.tell(
address === target
? `${formatted(this.text('sw_terminal_trace_msg4'), target, TRACEROUTE_MAXIMUM_HOPS)}\n`
: `${formatted(this.text('sw_terminal_trace_msg5'), target, address, TRACEROUTE_MAXIMUM_HOPS)}\n`,
);
const identifier = this.nextIdentifier();
let sequenceNumber = TRACEROUTE_FIRST_SEQUENCE_NUMBER;
let failures = 0;
let response: IcmpObservation | undefined;
let hops = 1;
// An answer to the probe: its echo reply, or an error that carries it (Java asks the error for identifier and
// number 0 too, which every error has, RFC 792).
const answers = (observation: IcmpObservation | undefined) => {
const message = observation?.message;
if (message === undefined) return false;
if (message.sequenceNumber === sequenceNumber && message.identifier === identifier) return true;
const carried = message.embeddedOriginalPacket?.payload;
return carried?.kind === 'icmp' && carried.message.sequenceNumber === sequenceNumber && carried.message.identifier === identifier;
};
for (; hops <= TRACEROUTE_MAXIMUM_HOPS && !this.interrupted; hops += 1) {
this.tell(` ${hops} `);
for (let attempt = 0; attempt < 3 && !this.interrupted; attempt += 1) {
sequenceNumber += 1;
response = yield* this.engine.icmpEchoSteps(this.nodeId, address, { identifier, sequenceNumber, timeToLive: hops });
if (answers(response)) {
failures = 0;
break;
}
failures += 1;
this.tell('* ');
}
if (failures === 0) {
this.tell(response!.packet.senderIPAddress);
if (response!.message.kind !== 'timeExceeded') break;
} else if (failures > 5) {
break;
}
this.tell('\n');
}
const [type, code] = response === undefined ? [undefined, undefined] : ICMP_TYPE_CODE[response.message.kind];
let summary: string;
if (hops > TRACEROUTE_MAXIMUM_HOPS) summary = formatted(this.text('sw_terminal_trace_msg6'), target, TRACEROUTE_MAXIMUM_HOPS);
else if (this.interrupted) summary = this.text('sw_terminal_trace_msg7');
else if (type === 3)
summary = formatted(this.text(code === 0 ? 'sw_terminal_trace_msg8' : code === 1 ? 'sw_terminal_trace_msg9' : 'sw_terminal_trace_msg10'), response!.packet.senderIPAddress);
else if (failures === 0) summary = hops === 1 ? formatted(this.text('sw_terminal_trace_msg11'), target) : formatted(this.text('sw_terminal_trace_msg12'), target, hops);
else summary = this.text('sw_terminal_trace_msg13');
this.tell(`\n\n${summary}`);
this.busy(false);
}
/**
* Terminal.tcpdump: each frame the node's first card logs from now on, as
* the capture's highest row (time, protocol, source > destination,
* comment), until Ctrl+C. Each frame at once (Java looks every 100 ms).
*/
private *tcpdump(): Steps {
this.busy(true);
this.tell(this.text('sw_terminal_msg55'));
const card = this.engine.networkInterfaces(this.nodeId)[0];
const frames = () => (card === undefined ? [] : this.engine.capture.frames(card.portId));
const start = this.engine.state.currentTimeMilliseconds;
let shown = frames().findLast((frame) => frame.timeMilliseconds < start)?.number ?? 0;
const grouping = CAPTURE_GROUPING[this.language];
const number = (value: number) => String(value).replace(/\B(?=(\d{3})+(?!\d))/g, grouping);
while (!this.interrupted) {
yield* waitUntil(() => this.interrupted || (frames().at(-1)?.number ?? 0) > shown || this.engine.state.phase !== 'running');
if (this.engine.state.phase !== 'running') return;
for (const frame of frames().filter((each) => each.number > shown)) {
const row = captureRows([frame], { withAddresses: true }).at(-1)!;
const time = timeOfDay(this.engine.wallTime(frame.timeMilliseconds));
const text = `${time} ${row.protocol} ${row.source} > ${row.destination} ${captureCommentText(row.comment, CAPTURE_WORDS[this.language], number)}`;
this.tell(`\n ${javaTrim(text).replaceAll('\n', ' ')}`);
shown = frame.number;
}
}
this.busy(false);
}
} }
+5 -1
View File
@@ -4203,7 +4203,6 @@ export class NetworkRuntimeEngine {
this.sendEthernetFrame(nodeId, networkInterface.portId, frame); this.sendEthernetFrame(nodeId, networkInterface.portId, frame);
} }
private earliestDeadline(filter: (kind: ScheduledEventKind) => boolean): number | undefined { private earliestDeadline(filter: (kind: ScheduledEventKind) => boolean): number | undefined {
let earliest: number | undefined; let earliest: number | undefined;
for (const event of this.stateValue.pendingEvents) { for (const event of this.stateValue.pendingEvents) {
@@ -4714,6 +4713,11 @@ export class NetworkRuntimeEngine {
return result; return result;
} }
/** The time of day (wallClock) when the simulation was at `milliseconds`, counted back from now. */
wallTime(milliseconds: number): number {
return this.wallClock() - (this.stateValue.currentTimeMilliseconds - milliseconds);
}
/** The first ICMP identifier of a command line (Terminal.setIdentifier: new Random().nextInt(43) * 23). */ /** The first ICMP identifier of a command line (Terminal.setIdentifier: new Random().nextInt(43) * 23). */
drawIcmpIdentifier(): number { drawIcmpIdentifier(): number {
return this.icmpIdentifierRandom.nextInt(43) * 23; return this.icmpIdentifierRandom.nextInt(43) * 23;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,543 @@
{
"frames": {
"00000000-0000-0000-0000-000000000401": [
{
"time": 506,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.31"
},
{
"time": 743,
"source": "02:00:00:00:04:14",
"destination": "02:00:00:00:04:11",
"arp": 2,
"senderIp": "192.168.0.31",
"targetIp": "192.168.0.20"
},
{
"time": 746,
"source": "02:00:00:00:04:11",
"destination": "02:00:00:00:04:14",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.0.20",
"destinationIp": "192.168.0.31",
"sequence": 967
},
{
"time": 973,
"source": "02:00:00:00:04:14",
"destination": "02:00:00:00:04:11",
"icmp": 0,
"icmpCode": 0,
"sourceIp": "192.168.0.31",
"destinationIp": "192.168.0.20",
"sequence": 967
}
],
"00000000-0000-0000-0000-000000000402": [
{
"time": 629,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.31"
}
],
"00000000-0000-0000-0000-000000000403": [
{
"time": 629,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.31"
}
],
"00000000-0000-0000-0000-000000000404": [
{
"time": 629,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.31"
},
{
"time": 631,
"source": "02:00:00:00:04:14",
"destination": "02:00:00:00:04:11",
"arp": 2,
"senderIp": "192.168.0.31",
"targetIp": "192.168.0.20"
},
{
"time": 860,
"source": "02:00:00:00:04:11",
"destination": "02:00:00:00:04:14",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.0.20",
"destinationIp": "192.168.0.31",
"sequence": 967
},
{
"time": 860,
"source": "02:00:00:00:04:14",
"destination": "02:00:00:00:04:11",
"icmp": 0,
"icmpCode": 0,
"sourceIp": "192.168.0.31",
"destinationIp": "192.168.0.20",
"sequence": 967
}
],
"00000000-0000-0000-0000-000000000405": [
{
"time": 629,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.31"
}
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"rows": {
"00000000-0000-0000-0000-000000000401": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
],
[
"2",
"02:00:00:00:04:14",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.31",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:04:14 [op=REPLY, sender=02:00:00:00:04:14|192.168.0.31, target=02:00:00:00:04:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:04:11",
"02:00:00:00:04:14",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.31",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 1, Identifier: 782, Seq.-No.: 967"
],
[
"4",
"02:00:00:00:04:14",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.31",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Echo Reply (pong), TTL: 64, Identifier: 782, Seq.-No.: 967"
]
],
"00000000-0000-0000-0000-000000000402": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
]
],
"00000000-0000-0000-0000-000000000403": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
]
],
"00000000-0000-0000-0000-000000000404": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
],
[
"2",
"02:00:00:00:04:14",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.31",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:04:14 [op=REPLY, sender=02:00:00:00:04:14|192.168.0.31, target=02:00:00:00:04:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:04:11",
"02:00:00:00:04:14",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.31",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 1, Identifier: 782, Seq.-No.: 967"
],
[
"4",
"02:00:00:00:04:14",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.31",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Echo Reply (pong), TTL: 64, Identifier: 782, Seq.-No.: 967"
]
],
"00000000-0000-0000-0000-000000000405": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
]
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"layeredRows": {
"00000000-0000-0000-0000-000000000401": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
],
[
"2",
"02:00:00:00:04:14",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.31",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:04:14 [op=REPLY, sender=02:00:00:00:04:14|192.168.0.31, target=02:00:00:00:04:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:04:11",
"02:00:00:00:04:14",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.31",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 1, Identifier: 782, Seq.-No.: 967"
],
[
"4",
"02:00:00:00:04:14",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.31",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Echo Reply (pong), TTL: 64, Identifier: 782, Seq.-No.: 967"
]
],
"00000000-0000-0000-0000-000000000402": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
]
],
"00000000-0000-0000-0000-000000000403": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
]
],
"00000000-0000-0000-0000-000000000404": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
],
[
"2",
"02:00:00:00:04:14",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.31",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:04:14 [op=REPLY, sender=02:00:00:00:04:14|192.168.0.31, target=02:00:00:00:04:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:04:11",
"02:00:00:00:04:14",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"192.168.0.31",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 1, Identifier: 782, Seq.-No.: 967"
],
[
"4",
"02:00:00:00:04:14",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.31",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Echo Reply (pong), TTL: 64, Identifier: 782, Seq.-No.: 967"
]
],
"00000000-0000-0000-0000-000000000405": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.31",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]"
]
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"acts": [
[
500,
"00000000-0000-0000-0000-000000000401",
"traceroute 192.168.0.31"
]
],
"events": [
[
501,
"00000000-0000-0000-0000-000000000401",
"call",
"traceroute 192.168.0.31"
],
[
502,
"00000000-0000-0000-0000-000000000401",
"busy",
"true"
],
[
502,
"00000000-0000-0000-0000-000000000401",
"text",
"Verbindung nach 192.168.0.31 wird bestimmt (max. 20 Stationen).\n"
],
[
502,
"00000000-0000-0000-0000-000000000401",
"text",
" 1 "
],
[
973,
"00000000-0000-0000-0000-000000000401",
"text",
"192.168.0.31"
],
[
973,
"00000000-0000-0000-0000-000000000401",
"busy",
"false"
],
[
973,
"00000000-0000-0000-0000-000000000401",
"text",
"\n\n192.168.0.31 wurde nach einem Sprung (hop) erreicht."
]
]
}
@@ -0,0 +1,729 @@
{
"frames": {
"00000000-0000-0000-0000-000000000401": [
{
"time": 506,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.1"
},
{
"time": 742,
"source": "02:00:00:00:04:51",
"destination": "02:00:00:00:04:11",
"arp": 2,
"senderIp": "192.168.0.1",
"targetIp": "192.168.0.20"
},
{
"time": 746,
"source": "02:00:00:00:04:11",
"destination": "02:00:00:00:04:51",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.0.20",
"destinationIp": "172.16.0.1",
"sequence": 967
},
{
"time": 971,
"source": "02:00:00:00:04:51",
"destination": "02:00:00:00:04:11",
"icmp": 11,
"icmpCode": 0,
"sourceIp": "192.168.0.1",
"destinationIp": "192.168.0.20",
"sequence": 0
},
{
"time": 971,
"source": "02:00:00:00:04:11",
"destination": "02:00:00:00:04:51",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.0.20",
"destinationIp": "172.16.0.1",
"sequence": 968
},
{
"time": 1194,
"source": "02:00:00:00:04:51",
"destination": "02:00:00:00:04:11",
"icmp": 3,
"icmpCode": 0,
"sourceIp": "192.168.0.1",
"destinationIp": "192.168.0.20",
"sequence": 0
}
],
"00000000-0000-0000-0000-000000000402": [
{
"time": 628,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.1"
}
],
"00000000-0000-0000-0000-000000000403": [
{
"time": 628,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.1"
}
],
"00000000-0000-0000-0000-000000000404": [
{
"time": 628,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.1"
}
],
"00000000-0000-0000-0000-000000000405": [
{
"time": 628,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.1"
},
{
"time": 630,
"source": "02:00:00:00:04:51",
"destination": "02:00:00:00:04:11",
"arp": 2,
"senderIp": "192.168.0.1",
"targetIp": "192.168.0.20"
},
{
"time": 859,
"source": "02:00:00:00:04:11",
"destination": "02:00:00:00:04:51",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.0.20",
"destinationIp": "172.16.0.1",
"sequence": 967
},
{
"time": 860,
"source": "02:00:00:00:04:51",
"destination": "02:00:00:00:04:11",
"icmp": 11,
"icmpCode": 0,
"sourceIp": "192.168.0.1",
"destinationIp": "192.168.0.20",
"sequence": 0
},
{
"time": 1082,
"source": "02:00:00:00:04:11",
"destination": "02:00:00:00:04:51",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.0.20",
"destinationIp": "172.16.0.1",
"sequence": 968
},
{
"time": 1083,
"source": "02:00:00:00:04:51",
"destination": "02:00:00:00:04:11",
"icmp": 3,
"icmpCode": 0,
"sourceIp": "192.168.0.1",
"destinationIp": "192.168.0.20",
"sequence": 0
}
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"rows": {
"00000000-0000-0000-0000-000000000401": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
],
[
"2",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.1",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:04:51 [op=REPLY, sender=02:00:00:00:04:51|192.168.0.1, target=02:00:00:00:04:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:04:11",
"02:00:00:00:04:51",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"172.16.0.1",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 1, Identifier: 184, Seq.-No.: 967"
],
[
"4",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.1",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Time Exeeded (poof), TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=1; ttl=1; protocol=1; sender=192.168.0.20; target=172.16.0.1; icmpType=8; icmpCode=0; identifier=184; seqNr=967]"
],
[
"5",
"02:00:00:00:04:11",
"02:00:00:00:04:51",
"",
"Netzzugang",
"0x800"
],
[
"5",
"192.168.0.20",
"172.16.0.1",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 2, Identifier: 184, Seq.-No.: 968"
],
[
"6",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"6",
"192.168.0.1",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Network Unreachable, TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=3; ttl=1; protocol=1; sender=null; target=172.16.0.1; icmpType=8; icmpCode=0; identifier=184; seqNr=968]"
]
],
"00000000-0000-0000-0000-000000000402": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
]
],
"00000000-0000-0000-0000-000000000403": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
]
],
"00000000-0000-0000-0000-000000000404": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
]
],
"00000000-0000-0000-0000-000000000405": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
],
[
"2",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.1",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:04:51 [op=REPLY, sender=02:00:00:00:04:51|192.168.0.1, target=02:00:00:00:04:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:04:11",
"02:00:00:00:04:51",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"172.16.0.1",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 1, Identifier: 184, Seq.-No.: 967"
],
[
"4",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.1",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Time Exeeded (poof), TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=1; ttl=1; protocol=1; sender=192.168.0.20; target=172.16.0.1; icmpType=8; icmpCode=0; identifier=184; seqNr=967]"
],
[
"5",
"02:00:00:00:04:11",
"02:00:00:00:04:51",
"",
"Netzzugang",
"0x800"
],
[
"5",
"192.168.0.20",
"172.16.0.1",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 2, Identifier: 184, Seq.-No.: 968"
],
[
"6",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"6",
"192.168.0.1",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Network Unreachable, TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=3; ttl=1; protocol=1; sender=null; target=172.16.0.1; icmpType=8; icmpCode=0; identifier=184; seqNr=968]"
]
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"layeredRows": {
"00000000-0000-0000-0000-000000000401": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
],
[
"2",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.1",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:04:51 [op=REPLY, sender=02:00:00:00:04:51|192.168.0.1, target=02:00:00:00:04:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:04:11",
"02:00:00:00:04:51",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"172.16.0.1",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 1, Identifier: 184, Seq.-No.: 967"
],
[
"4",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.1",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Time Exeeded (poof), TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=1; ttl=1; protocol=1; sender=192.168.0.20; target=172.16.0.1; icmpType=8; icmpCode=0; identifier=184; seqNr=967]"
],
[
"5",
"02:00:00:00:04:11",
"02:00:00:00:04:51",
"",
"Netzzugang",
"0x800"
],
[
"5",
"192.168.0.20",
"172.16.0.1",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 2, Identifier: 184, Seq.-No.: 968"
],
[
"6",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"6",
"192.168.0.1",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Network Unreachable, TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=3; ttl=1; protocol=1; sender=null; target=172.16.0.1; icmpType=8; icmpCode=0; identifier=184; seqNr=968]"
]
],
"00000000-0000-0000-0000-000000000402": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
]
],
"00000000-0000-0000-0000-000000000403": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
]
],
"00000000-0000-0000-0000-000000000404": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
]
],
"00000000-0000-0000-0000-000000000405": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.1",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.1 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.1]"
],
[
"2",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.1",
"192.168.0.20",
"ARP",
"Vermittlung",
"MAC ist 02:00:00:00:04:51 [op=REPLY, sender=02:00:00:00:04:51|192.168.0.1, target=02:00:00:00:04:11|192.168.0.20]"
],
[
"3",
"02:00:00:00:04:11",
"02:00:00:00:04:51",
"",
"Netzzugang",
"0x800"
],
[
"3",
"192.168.0.20",
"172.16.0.1",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 1, Identifier: 184, Seq.-No.: 967"
],
[
"4",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"4",
"192.168.0.1",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Time Exeeded (poof), TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=1; ttl=1; protocol=1; sender=192.168.0.20; target=172.16.0.1; icmpType=8; icmpCode=0; identifier=184; seqNr=967]"
],
[
"5",
"02:00:00:00:04:11",
"02:00:00:00:04:51",
"",
"Netzzugang",
"0x800"
],
[
"5",
"192.168.0.20",
"172.16.0.1",
"ICMP",
"Vermittlung",
"ICMP Echo Request (ping), TTL: 2, Identifier: 184, Seq.-No.: 968"
],
[
"6",
"02:00:00:00:04:51",
"02:00:00:00:04:11",
"",
"Netzzugang",
"0x800"
],
[
"6",
"192.168.0.1",
"192.168.0.20",
"ICMP",
"Vermittlung",
"ICMP Network Unreachable, TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=3; ttl=1; protocol=1; sender=null; target=172.16.0.1; icmpType=8; icmpCode=0; identifier=184; seqNr=968]"
]
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"acts": [
[
500,
"00000000-0000-0000-0000-000000000401",
"traceroute 172.16.0.1"
]
],
"events": [
[
500,
"00000000-0000-0000-0000-000000000401",
"call",
"traceroute 172.16.0.1"
],
[
501,
"00000000-0000-0000-0000-000000000401",
"busy",
"true"
],
[
501,
"00000000-0000-0000-0000-000000000401",
"text",
"Verbindung nach 172.16.0.1 wird bestimmt (max. 20 Stationen).\n"
],
[
501,
"00000000-0000-0000-0000-000000000401",
"text",
" 1 "
],
[
971,
"00000000-0000-0000-0000-000000000401",
"text",
"192.168.0.1"
],
[
971,
"00000000-0000-0000-0000-000000000401",
"text",
"\n"
],
[
971,
"00000000-0000-0000-0000-000000000401",
"text",
" 2 "
],
[
1194,
"00000000-0000-0000-0000-000000000401",
"text",
"192.168.0.1"
],
[
1194,
"00000000-0000-0000-0000-000000000401",
"busy",
"false"
],
[
1194,
"00000000-0000-0000-0000-000000000401",
"text",
"\n\nFehler: 192.168.0.1 antwortet \"ICMP Network Unreachable\""
]
]
}
@@ -0,0 +1,495 @@
{
"frames": {
"00000000-0000-0000-0000-000000000401": [
{
"time": 507,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
},
{
"time": 3257,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
}
],
"00000000-0000-0000-0000-000000000402": [
{
"time": 628,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
},
{
"time": 3369,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
}
],
"00000000-0000-0000-0000-000000000403": [
{
"time": 628,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
},
{
"time": 3369,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
}
],
"00000000-0000-0000-0000-000000000404": [
{
"time": 628,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
},
{
"time": 3369,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
}
],
"00000000-0000-0000-0000-000000000405": [
{
"time": 628,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
},
{
"time": 3369,
"source": "02:00:00:00:04:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.0.20",
"targetIp": "192.168.0.99"
}
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"rows": {
"00000000-0000-0000-0000-000000000401": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000402": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000403": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000404": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000405": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"layeredRows": {
"00000000-0000-0000-0000-000000000401": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000402": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000403": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000404": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000405": [
[
"1",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"1",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
],
[
"2",
"02:00:00:00:04:11",
"FF:FF:FF:FF:FF:FF",
"",
"Netzzugang",
"0x806"
],
[
"2",
"192.168.0.20",
"192.168.0.99",
"ARP",
"Vermittlung",
"Suche nach MAC für 192.168.0.99 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.99]"
]
],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"acts": [
[
500,
"00000000-0000-0000-0000-000000000401",
"traceroute 192.168.0.99"
]
],
"events": [
[
500,
"00000000-0000-0000-0000-000000000401",
"call",
"traceroute 192.168.0.99"
],
[
501,
"00000000-0000-0000-0000-000000000401",
"busy",
"true"
],
[
502,
"00000000-0000-0000-0000-000000000401",
"text",
"Verbindung nach 192.168.0.99 wird bestimmt (max. 20 Stationen).\n"
],
[
502,
"00000000-0000-0000-0000-000000000401",
"text",
" 1 "
],
[
6012,
"00000000-0000-0000-0000-000000000401",
"text",
"127.0.0.1"
],
[
6012,
"00000000-0000-0000-0000-000000000401",
"busy",
"false"
],
[
6012,
"00000000-0000-0000-0000-000000000401",
"text",
"\n\nFehler: 127.0.0.1 antwortet \"ICMP Host Unreachable\""
]
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,127 @@
{
"frames": {
"00000000-0000-0000-0000-000000000401": [],
"00000000-0000-0000-0000-000000000402": [],
"00000000-0000-0000-0000-000000000403": [],
"00000000-0000-0000-0000-000000000404": [],
"00000000-0000-0000-0000-000000000405": [],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"rows": {
"00000000-0000-0000-0000-000000000401": [],
"00000000-0000-0000-0000-000000000402": [],
"00000000-0000-0000-0000-000000000403": [],
"00000000-0000-0000-0000-000000000404": [],
"00000000-0000-0000-0000-000000000405": [],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"layeredRows": {
"00000000-0000-0000-0000-000000000401": [],
"00000000-0000-0000-0000-000000000402": [],
"00000000-0000-0000-0000-000000000403": [],
"00000000-0000-0000-0000-000000000404": [],
"00000000-0000-0000-0000-000000000405": [],
"00000000-0000-0000-0000-000000000405#1": [],
"00000000-0000-0000-0000-000000000406": []
},
"acts": [
[
500,
"00000000-0000-0000-0000-000000000401",
"traceroute 192.168.0.20"
],
[
1500,
"00000000-0000-0000-0000-000000000401",
"traceroute localhost"
]
],
"events": [
[
501,
"00000000-0000-0000-0000-000000000401",
"call",
"traceroute 192.168.0.20"
],
[
502,
"00000000-0000-0000-0000-000000000401",
"busy",
"true"
],
[
502,
"00000000-0000-0000-0000-000000000401",
"text",
"Verbindung nach 192.168.0.20 wird bestimmt (max. 20 Stationen).\n"
],
[
502,
"00000000-0000-0000-0000-000000000401",
"text",
" 1 "
],
[
507,
"00000000-0000-0000-0000-000000000401",
"text",
"127.0.0.1"
],
[
507,
"00000000-0000-0000-0000-000000000401",
"busy",
"false"
],
[
507,
"00000000-0000-0000-0000-000000000401",
"text",
"\n\n192.168.0.20 wurde nach einem Sprung (hop) erreicht."
],
[
1500,
"00000000-0000-0000-0000-000000000401",
"call",
"traceroute localhost"
],
[
1501,
"00000000-0000-0000-0000-000000000401",
"busy",
"true"
],
[
1501,
"00000000-0000-0000-0000-000000000401",
"text",
"Verbindung nach localhost (127.0.0.1) wird bestimmt (max. 20 Stationen).\n"
],
[
1501,
"00000000-0000-0000-0000-000000000401",
"text",
" 1 "
],
[
1501,
"00000000-0000-0000-0000-000000000401",
"text",
"127.0.0.1"
],
[
1501,
"00000000-0000-0000-0000-000000000401",
"busy",
"false"
],
[
1502,
"00000000-0000-0000-0000-000000000401",
"text",
"\n\nlocalhost wurde nach einem Sprung (hop) erreicht."
]
]
}
File diff suppressed because it is too large Load Diff
+184 -5
View File
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { NetworkRuntimeEngine, withSnapshot } from '../src/index.js'; import { NetworkRuntimeEngine, withSnapshot } from '../src/index.js';
import { expectSameCaptures, GAP_TOLERANCE_MILLISECONDS, watchedEngine, withClientPortText, type JavaFrame, type JavaTables, type SeenFrame } from './javaRun.js'; import { expectSameCaptures, GAP_TOLERANCE_MILLISECONDS, watchedEngine, withClientPortText, type JavaFrame, type JavaTables, type SeenFrame } from './javaRun.js';
import { POLL_MILLISECONDS } from './programLines.js';
import { snapshotOf, type Scenario } from './scenario.js'; import { snapshotOf, type Scenario } from './scenario.js';
const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios'); const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios');
@@ -33,7 +34,29 @@ interface JavaRun extends JavaTables {
events: [number, string, string, string][]; events: [number, string, string, string][];
} }
const STORIES = ['pingLan', 'pingRouted', 'pingNobody', 'pingUnreachable', 'pingBroadcast', 'pingSelf', 'pingErrors', 'interrupt', 'dns', 'dnsTimeout', 'arp', 'netstat'] as const; const STORIES = [
'pingLan',
'pingRouted',
'pingNobody',
'pingUnreachable',
'pingBroadcast',
'pingSelf',
'pingErrors',
'interrupt',
'dns',
'dnsTimeout',
'arp',
'netstat',
'traceLan',
'traceRouted',
'traceUnreachable',
'traceNoRoute',
'traceNobody',
'traceSelf',
'traceErrors',
'traceInterrupt',
'tcpdump',
] as const;
type Lines = Record<string, [number, Told][]>; type Lines = Record<string, [number, Told][]>;
@@ -68,7 +91,12 @@ function lineText(text: string): string {
const protocol = (row: string) => protocols.indexOf(/^\| (TCP|UDP) /.exec(row)?.[1] ?? ''); const protocol = (row: string) => protocols.indexOf(/^\| (TCP|UDP) /.exec(row)?.[1] ?? '');
rows.sort((lhs, rhs) => protocol(lhs) - protocol(rhs) || (lhs < rhs ? -1 : lhs > rhs ? 1 : 0)); rows.sort((lhs, rhs) => protocol(lhs) - protocol(rhs) || (lhs < rhs ? -1 : lhs > rhs ? 1 : 0));
const sorted = table === null ? text : `${table[1]}${rows.join('\n')}\n${table[3] ?? ''}`; const sorted = table === null ? text : `${table[1]}${rows.join('\n')}\n${table[3] ?? ''}`;
return withClientPortText(sorted).replace(/time=\d+ms/, 'time=…ms'); // tcpdump: a frame's time of day, a ping's identifier, a DNS query's ID.
return withClientPortText(sorted)
.replace(/time=\d+ms/, 'time=…ms')
.replace(/^\n {2}\d{2}:\d{2}:\d{2}\.\d{3} /, '\n <time> ')
.replace(/Identifier: \d+/, 'Identifier: x')
.replace(/ID=\d+/, 'ID=x');
} }
const pingTime = (text: string) => Number(/time=(\d+)ms/.exec(text)?.[1] ?? 0); const pingTime = (text: string) => Number(/time=(\d+)ms/.exec(text)?.[1] ?? 0);
@@ -116,6 +144,21 @@ function withCells(java: JavaRun, change: (cell: string) => string): JavaRun {
return { ...java, rows: tables(java.rows), layeredRows: tables(java.layeredRows) }; return { ...java, rows: tables(java.rows), layeredRows: tables(java.layeredRows) };
} }
/** traceroute: the summary told before the end mark (Java tells it after, where its window shows no more text). */
function withSummaryBeforeEnd(java: JavaRun): JavaRun {
const events = [...java.events];
for (let index = 0; index + 1 < events.length; index += 1) {
const [time, host, kind, text] = events[index]!;
const next = events.findIndex((each, at) => at > index && each[1] === host);
if (kind !== 'busy' || text !== 'false' || next === -1 || events[next]![2] !== 'text' || !events[next]![3].startsWith('\n\n')) continue;
const summary = events[next]!;
events.splice(next, 1);
events.splice(index, 0, [time, host, 'text', summary[3]]);
index += 1;
}
return { ...java, events };
}
const USAGE_PING = 'ping - test connection to other computer\nUsage: ping <remote host> \n <remote host> the remote address as IP address or host name\n'; const USAGE_PING = 'ping - test connection to other computer\nUsage: ping <remote host> \n <remote host> the remote address as IP address or host name\n';
/** /**
@@ -129,6 +172,9 @@ const USAGE_PING = 'ping - test connection to other computer\nUsage: ping <remot
* a socket for each address and port it ever answered (never closed). * a socket for each address and port it ever answered (never closed).
* - A router's network unreachable carries the echo as it would have * - A router's network unreachable carries the echo as it would have
* forwarded it, with its sender (Java's is half made: sender null). * forwarded it, with its sender (Java's is half made: sender null).
* - traceroute tells its summary before the end mark (withSummaryBeforeEnd);
* the host unreachable a host gives itself comes from its address (Java:
* 127.0.0.1, as its answer to itself).
*/ */
const JAVA_BUGS: Partial<Record<(typeof STORIES)[number], (java: JavaRun) => JavaRun>> = { const JAVA_BUGS: Partial<Record<(typeof STORIES)[number], (java: JavaRun) => JavaRun>> = {
pingBroadcast: (java) => pingBroadcast: (java) =>
@@ -156,12 +202,39 @@ const JAVA_BUGS: Partial<Record<(typeof STORIES)[number], (java: JavaRun) => Jav
); );
}, },
pingUnreachable: (java) => withCells(java, (cell) => cell.replace('sender=null;', 'sender=192.168.0.20;')), pingUnreachable: (java) => withCells(java, (cell) => cell.replace('sender=null;', 'sender=192.168.0.20;')),
traceLan: withSummaryBeforeEnd,
traceRouted: (java) => {
const typed = java.acts.find(([, , line]) => line === 'traceroute 10.0.0.1')![0];
return withEchoReplySource(
withSummaryBeforeEnd(withTold(java, (_, text, time) => (time >= typed && text === '192.168.0.1' ? '10.0.0.1' : text))),
'192.168.0.1',
'10.0.0.1',
typed,
);
},
traceUnreachable: withSummaryBeforeEnd,
traceNoRoute: (java) => withSummaryBeforeEnd(withCells(java, (cell) => cell.replace('sender=null;', 'sender=192.168.0.20;'))),
traceNobody: (java) => withSummaryBeforeEnd(withTold(java, (_, text) => text.replace('127.0.0.1', '192.168.0.20'))),
traceSelf: (java) => withSummaryBeforeEnd(withTold(java, (_, text, time) => (time < 1_500 ? text.replace('127.0.0.1', '192.168.0.20') : text))),
traceInterrupt: withSummaryBeforeEnd,
netstat: (java) => withTold(java, (host, text) => (host === DNS ? text.replace(/\| UDP {5}\| {4}192\.168\.0\.10:53 {4}\|[^\n]*CONNECTED +\|\n/g, '') : text)), netstat: (java) => withTold(java, (host, text) => (host === DNS ? text.replace(/\| UDP {5}\| {4}192\.168\.0\.10:53 {4}\|[^\n]*CONNECTED +\|\n/g, '') : text)),
}; };
/** Where the frames are compared only up to a time (Java's): pingErrors, the ping Java sends for ping -x. */ /**
* Where the frames are compared only up to a time (Java's): pingErrors, the
* ping Java sends for ping -x; traceUnreachable, from the sixth probe on:
* Java's router logs it 2.5 s late. Its ICMP thread waits for ARP inside
* the lock of its buffer (ProtokollThread.run), the card's thread blocks on
* handing it the next frame, and the frames behind are read, and logged,
* only when ARP gives up; the engine logs a frame when it comes. (The texts
* are the same either way.)
*/
/** traceroute's sixth probe: its numbers count on from 42 * 23. */
const TRACEROUTE_SIXTH_PROBE = 42 * 23 + 6;
const UNTIL: Partial<Record<(typeof STORIES)[number], (java: JavaRun) => number>> = { const UNTIL: Partial<Record<(typeof STORIES)[number], (java: JavaRun) => number>> = {
pingErrors: (java) => java.acts.find(([, , line]) => line.startsWith('ping -x'))![0], pingErrors: (java) => java.acts.find(([, , line]) => line.startsWith('ping -x'))![0],
traceUnreachable: (java) => java.frames[CLIENT]!.find((frame) => frame.icmp === 8 && frame.sequence === TRACEROUTE_SIXTH_PROBE)!.time + 50,
}; };
/** The hosts that answer a broadcast echo at once: which answer comes first is a race of Java's threads. */ /** The hosts that answer a broadcast echo at once: which answer comes first is a race of Java's threads. */
@@ -211,7 +284,7 @@ describe('the command line on the network as Java FILIUS runs it', () => {
const snapshot = withSnapshot(snapshotOf(scenario), { const snapshot = withSnapshot(snapshotOf(scenario), {
language: 'de', language: 'de',
terminalNodeIds: new Set(HOSTS), terminalNodeIds: new Set(HOSTS),
...(name === 'netstat' ...(name === 'netstat' || name === 'tcpdump'
? { ? {
echoServersByNodeId: new Map([ echoServersByNodeId: new Map([
[ECHO, { port: 55555, isActive: true }], [ECHO, { port: 55555, isActive: true }],
@@ -227,6 +300,7 @@ describe('the command line on the network as Java FILIUS runs it', () => {
() => { () => {
if (line === '^C') engine.terminal(host)!.interrupt(); if (line === '^C') engine.terminal(host)!.interrupt();
else if (line === 'disconnect') engine.simpleClient(CLIENT)!.disconnect(); else if (line === 'disconnect') engine.simpleClient(CLIENT)!.disconnect();
else if (line === 'connect') engine.simpleClient(CLIENT)!.connect('192.168.0.31', 55555);
else engine.terminal(host)!.enter(line); else engine.terminal(host)!.enter(line);
}, },
]); ]);
@@ -264,7 +338,10 @@ describe('the command line on the network as Java FILIUS runs it', () => {
for (const [index, [javaTime, line]] of expected[host]!.entries()) { for (const [index, [javaTime, line]] of expected[host]!.entries()) {
const [engineTime, engineLine] = actual[host]![index]!; const [engineTime, engineLine] = actual[host]![index]!;
const late = javaTime - engineTime; const late = javaTime - engineTime;
expect(Math.abs(late), `${host}'s line ${JSON.stringify(line[1].slice(0, 60))}: Java ${late} ms later`).toBeLessThanOrEqual(GAP_TOLERANCE_MILLISECONDS); expect(Math.abs(late), `${host}'s line ${JSON.stringify(line[1].slice(0, 60))}: Java ${late} ms later`).toBeLessThanOrEqual(
// tcpdump: Java looks for new frames every 100 ms.
GAP_TOLERANCE_MILLISECONDS + (name === 'tcpdump' ? POLL_MILLISECONDS : 0),
);
expect(Math.abs(pingTime(line[1]) - pingTime(engineLine[1])), `${host}'s ping time in ${JSON.stringify(line[1])}`).toBeLessThanOrEqual(GAP_TOLERANCE_MILLISECONDS); expect(Math.abs(pingTime(line[1]) - pingTime(engineLine[1])), `${host}'s ping time in ${JSON.stringify(line[1])}`).toBeLessThanOrEqual(GAP_TOLERANCE_MILLISECONDS);
} }
} }
@@ -408,4 +485,106 @@ describe('the command line on the network beyond the stories', () => {
expect(rows).toHaveLength(2); expect(rows).toHaveLength(2);
expect(engine.routeRow(CLIENT, '192.168.0.31')).toEqual(rows[0]); expect(engine.routeRow(CLIENT, '192.168.0.31')).toEqual(rows[0]);
}); });
/**
* 19 routers in a row, the host behind the last: 20 hops. Java says the
* host could not be reached (it asks ttl >= 20 after the loop, also when
* the 20th hop answered); measured only up to two hops (traceRouted).
*/
it('says a host was reached at the 20th hop', () => {
const id = (number: number) => `00000000-0000-0000-0000-${String(number).padStart(12, '0')}`;
const ROUTERS = 19;
const client = id(1);
const host = id(2);
const router = (index: number) => id(100 + index);
const port = (node: number, side: number) => id(1000 + node * 2 + side);
const nodes: Scenario['nodes'] = [
{ id: client, kind: 'pc', ports: [{ id: port(1, 0), label: 'eth0' }] },
{ id: host, kind: 'pc', ports: [{ id: port(2, 0), label: 'eth0' }] },
...Array.from({ length: ROUTERS }, (_, index) => ({
id: router(index + 1),
kind: 'router' as const,
ports: [
{ id: port(100 + index + 1, 0), label: 'eth0' },
{ id: port(100 + index + 1, 1), label: 'eth1' },
],
})),
];
const ends = [port(1, 0), ...Array.from({ length: ROUTERS }, (_, index) => [port(101 + index, 0), port(101 + index, 1)]).flat(), port(2, 0)];
const owner = (portId: string) => nodes.find((node) => node.ports.some((each) => each.id === portId))!.id;
const links: Scenario['links'] = Array.from({ length: ROUTERS + 1 }, (_, index) => ({
id: id(5000 + index),
sourceNode: owner(ends[2 * index]!),
sourcePort: ends[2 * index]!,
targetNode: owner(ends[2 * index + 1]!),
targetPort: ends[2 * index + 1]!,
}));
// Network k is 10.k.0.0/24; router i has 10.i.0.1 towards the client and 10.(i+1).0.2 towards the host.
const mask = '255.255.255.0';
const chain: Scenario = {
seed: 7,
nodes,
links,
devices: {
[client]: { ip: '10.1.0.10', mask, gateway: '10.1.0.1' },
[host]: { ip: `10.${ROUTERS + 1}.0.10`, mask, gateway: `10.${ROUTERS + 1}.0.2` },
...Object.fromEntries(Array.from({ length: ROUTERS }, (_, index) => [router(index + 1), { ip: `10.${index + 1}.0.1`, mask }])),
},
interfaces: Array.from({ length: ROUTERS }, (_, index) => [
{ node: router(index + 1), port: port(101 + index, 0), ip: `10.${index + 1}.0.1`, mask },
{ node: router(index + 1), port: port(101 + index, 1), ip: `10.${index + 2}.0.2`, mask },
]).flat(),
routes: Object.fromEntries(
Array.from({ length: ROUTERS }, (_, index) => {
const number = index + 1;
const back = number === 1 ? [] : [{ destination: '10.1.0.0', mask, gateway: `10.${number}.0.2`, interface: `10.${number}.0.1` }];
const on = number === ROUTERS ? [] : [{ destination: `10.${ROUTERS + 1}.0.0`, mask, gateway: `10.${number + 1}.0.1`, interface: `10.${number + 1}.0.2` }];
return [router(number), [...back, ...on]];
}),
),
};
const engine = watchedEngine(chain, withSnapshot(snapshotOf(chain), { language: 'de', terminalNodeIds: new Set([client]) })).engine;
engine.terminal(client)!.enter(`traceroute 10.${ROUTERS + 1}.0.10`);
engine.handle({ type: 'advance', toMilliseconds: 120_000 });
const told = engine.terminal(client)!.log.all.flatMap((line) => (line.kind === 'text' ? [line.text] : []));
expect(told.slice(-3)).toEqual([' 20 ', '10.20.0.10', '\n\n10.20.0.10 wurde nach 20 Sprüngen (hops) erreicht.']);
});
it("takes no echo reply of another identifier for traceroute's probe", () => {
const engine = started();
engine.terminal(CLIENT)!.enter('traceroute 192.168.0.31');
queueEchoReply(engine, 42 * 23 + 1);
engine.handle({ type: 'advance', toMilliseconds: 10_000 });
// The first probe gets the other reply: a star (and then the late answer to it comes first for the next).
expect(texts(engine).slice(1, 3)).toEqual([' 1 ', '* ']);
});
it('dumps only the frames from its start, each with its time of day', () => {
const scenario = JSON.parse(readFileSync(join(scenarioDirectory, 'terminal-net.scenario.json'), 'utf8')) as Scenario;
// The first frame comes about 110 ms after 3 s: at a time of day with two digits of milliseconds (Java pads to three).
const base = new Date(2026, 9, 2, 10, 37, 6, 900).getTime();
const engine = new NetworkRuntimeEngine(scenario.seed, undefined, { wallClock: () => base + engine.state.currentTimeMilliseconds });
engine.handle({
type: 'start',
snapshot: withSnapshot(snapshotOf(scenario), { language: 'de', terminalNodeIds: new Set(HOSTS) }),
seed: scenario.seed,
initialTimeMilliseconds: 0,
});
engine.terminal(CLIENT)!.enter('host www.filius.de');
engine.handle({ type: 'advance', toMilliseconds: 3_000 });
engine.terminal(ECHO)!.enter('tcpdump');
engine.terminal(CLIENT)!.enter('ping 192.168.0.31');
engine.handle({ type: 'advance', toMilliseconds: 3_900 });
const card = scenario.nodes.find((node) => node.id === ECHO)!.ports[0]!.id;
const first = engine.capture.frames(card).find((frame) => frame.timeMilliseconds >= 3_000)!;
const time = new Date(base + first.timeMilliseconds);
const two = (value: number) => String(value).padStart(2, '0');
expect(time.getMilliseconds()).toBeLessThan(100);
const expected = `${two(time.getHours())}:${two(time.getMinutes())}:${two(time.getSeconds())}.${String(time.getMilliseconds()).padStart(3, '0')}`;
const dumped = engine.terminal(ECHO)!.log.all.flatMap((line) => (line.kind === 'text' ? [line.text] : []));
// The ARP request of the lookup before is not dumped: the ping's ARP request first.
expect(dumped[1]).toBe(
`\n ${expected} ARP 192.168.0.20 > 192.168.0.31 Suche nach MAC f\u00fcr 192.168.0.31 [op=REQUEST, sender=02:00:00:00:04:11|192.168.0.20, target=FF:FF:FF:FF:FF:FF|192.168.0.31]`,
);
});
}); });
+27
View File
@@ -861,3 +861,30 @@ Gemessen (02.10.2026):
über 1 s, mehr als 6 s zwischen zwei Timeouts ins Leere) wird wiederholt; über 1 s, mehr als 6 s zwischen zwei Timeouts ins Leere) wird wiederholt;
ebenso eine unter Last (andere Sitzungen auf dem Rechner: die erste ebenso eine unter Last (andere Sitzungen auf dem Rechner: die erste
ARP-Runde dauert dann knapp 300 statt 225 ms). ARP-Runde dauert dann knapp 300 statt 225 ms).
## Befehlszeile, traceroute und tcpdump (F3.4c)
Weitere Abläufe von `TerminalRun` auf `terminal-net`. Gemessen (02.10.2026):
* `traceroute`: je Sprung bis zu drei Proben (Echo mit TTL), je eine RTT
auf die erste ICMP-Nachricht; eine Antwort zählt, wenn sie die Probe
trägt (Echo-Antwort mit ihrer Nummer, oder ein Fehler mit 0/0 und der
Probe darin), sonst „* “. Die Fehlzahl wird nur bei einem Treffer
zurückgesetzt: nach mehr als fünf Fehlschlägen bricht Java ab. Die
Nummern zählen ab 42 · 23, der Identifier ist der von `ping`.
* Das Fazit kommt nach der Laufmarke „fertig“, das Fenster zeigt es dann
nicht mehr (`GUIApplicationTerminalWindow.update` zeigt Text nur, solange
ein Befehl läuft). Erreicht das Ziel den 20. Sprung, nennt Java es
„nicht erreicht“ (`ttl >= maxHops`, aus dem Code; gemessen bis zwei
Sprünge).
* Eigene Adresse, ferne Router-Adresse, das „Host Unreachable“, das sich ein
Rechner selbst gibt: Java nennt 127.0.0.1 bzw. die nahe Karte.
* Hinter dem Router ohne Empfänger: Der Router hält während seiner
ARP-Suche die Sperre seines ICMP-Puffers (`ProtokollThread.run`
verarbeitet in `synchronized`); der Ethernet-Thread seiner Karte blockiert
beim Übergeben, und Rahmen dahinter liest und protokolliert die Karte erst
danach (die sechste Probe 2,5 s zu spät).
* `tcpdump`: je Rahmen der ersten Karte ab dem Start die oberste Zeile der
Tabelle (Uhrzeit, Protokoll, Quelle > Ziel, Kommentar), alle 100 ms;
Zeilenumbrüche werden zu zwei Leerzeichen; Strg+C beendet es nach dem
nächsten Blick. Ein während dessen getippter Befehl wartet bis danach.
+3 -1
View File
@@ -289,7 +289,9 @@ done
# The command line's network commands on terminal-net, typed at set times # The command line's network commands on terminal-net, typed at set times
# (TerminalRun; packages/core/test/terminalJava.test.ts), in the German locale. # (TerminalRun; packages/core/test/terminalJava.test.ts), in the German locale.
for run in pingLan:8000 pingRouted:13000 pingNobody:49000 pingUnreachable:45000 pingBroadcast:27000 pingSelf:17000 \ for run in pingLan:8000 pingRouted:13000 pingNobody:49000 pingUnreachable:45000 pingBroadcast:27000 pingSelf:17000 \
pingErrors:14000 interrupt:16000 dns:15000 dnsTimeout:80000 arp:11000 netstat:10000; do pingErrors:14000 interrupt:16000 dns:15000 dnsTimeout:80000 arp:11000 netstat:10000 traceLan:4000 \
traceRouted:6000 traceUnreachable:35000 traceNoRoute:4000 traceNobody:12000 traceSelf:3500 traceErrors:20000 \
traceInterrupt:12000 tcpdump:10000; do
story="${run%%:*}" story="${run%%:*}"
output="$root/packages/core/test/scenarios/terminal-java-$story.java.json" output="$root/packages/core/test/scenarios/terminal-java-$story.java.json"
rm -f "$output" rm -f "$output"
+60 -2
View File
@@ -31,6 +31,16 @@
// arp arp after a ping and a lookup, -a, -d, unknown options, no address, the broadcast row removed // arp arp after a ping and a lookup, -a, -d, unknown options, no address, the broadcast row removed
// netstat netstat on the client connected to the echo server, on the echo server, on the DNS server; // netstat netstat on the client connected to the echo server, on the echo server, on the DNS server;
// the connection closed, on both again // the connection closed, on both again
// traceLan traceroute .31 (one hop)
// traceRouted traceroute fern.filius.de (two hops), the router's far address
// traceUnreachable traceroute 10.0.0.99 (no one behind the router)
// traceNoRoute traceroute 172.16.0.1 (no route at the router)
// traceNobody traceroute .99 (no one in the LAN)
// traceSelf traceroute its own address, localhost
// traceErrors usage, an unknown name, no DNS server
// traceInterrupt traceroute 10.0.0.99, Ctrl+C after 3.5 s
// tcpdump tcpdump on .31 and on the DNS server while the client pings .31, looks up a name
// and connects to .31's echo server; arp typed on .31 meanwhile; Ctrl+C on both
import filius.software.clientserver.ClientBaustein; import filius.software.clientserver.ClientBaustein;
import filius.software.clientserver.ServerBaustein; import filius.software.clientserver.ServerBaustein;
@@ -167,6 +177,46 @@ public class TerminalRun {
acts.add(new Object[] { 8000L, CLIENT, "netstat" }); acts.add(new Object[] { 8000L, CLIENT, "netstat" });
acts.add(new Object[] { 8200L, ECHO, "netstat" }); acts.add(new Object[] { 8200L, ECHO, "netstat" });
break; break;
case "traceLan":
acts.add(new Object[] { 500L, CLIENT, "traceroute 192.168.0.31" });
break;
case "traceRouted":
acts.add(new Object[] { 500L, CLIENT, "traceroute fern.filius.de" });
acts.add(new Object[] { 3000L, CLIENT, "traceroute 10.0.0.1" });
break;
case "traceUnreachable":
acts.add(new Object[] { 500L, CLIENT, "traceroute 10.0.0.99" });
break;
case "traceNoRoute":
acts.add(new Object[] { 500L, CLIENT, "traceroute 172.16.0.1" });
break;
case "traceNobody":
acts.add(new Object[] { 500L, CLIENT, "traceroute 192.168.0.99" });
break;
case "traceSelf":
acts.add(new Object[] { 500L, CLIENT, "traceroute 192.168.0.20" });
acts.add(new Object[] { 1500L, CLIENT, "traceroute localhost" });
break;
case "traceErrors":
acts.add(new Object[] { 500L, CLIENT, "traceroute" });
acts.add(new Object[] { 700L, CLIENT, "traceroute a b" });
acts.add(new Object[] { 900L, CLIENT, "traceroute gibtsnicht.filius.de" });
acts.add(new Object[] { 3000L, FAR, "traceroute www.filius.de" });
break;
case "traceInterrupt":
acts.add(new Object[] { 500L, CLIENT, "traceroute 10.0.0.99" });
acts.add(new Object[] { 4000L, CLIENT, "^C" });
break;
case "tcpdump":
acts.add(new Object[] { 500L, ECHO, "tcpdump" });
acts.add(new Object[] { 600L, DNS, "tcpdump" });
acts.add(new Object[] { 1000L, CLIENT, "ping 192.168.0.31" });
acts.add(new Object[] { 3000L, ECHO, "arp" });
acts.add(new Object[] { 5500L, CLIENT, "host www.filius.de" });
acts.add(new Object[] { 6500L, CLIENT, "connect" });
acts.add(new Object[] { 8000L, ECHO, "^C" });
acts.add(new Object[] { 8100L, DNS, "^C" });
break;
default: default:
throw new IllegalArgumentException("unknown story " + story); throw new IllegalArgumentException("unknown story " + story);
} }
@@ -205,7 +255,7 @@ public class TerminalRun {
terminals.put(host, terminal); terminals.put(host, terminal);
} }
ClientBaustein client = null; ClientBaustein client = null;
if (story.equals("netstat")) { if (story.equals("netstat") || story.equals("tcpdump")) {
for (String host : new String[] { ECHO, DNS }) { for (String host : new String[] { ECHO, DNS }) {
network.system(host).installApp("filius.software.clientserver.ServerBaustein", false); network.system(host).installApp("filius.software.clientserver.ServerBaustein", false);
ServerBaustein server = (ServerBaustein) network.system(host).holeSoftware("filius.software.clientserver.ServerBaustein"); ServerBaustein server = (ServerBaustein) network.system(host).holeSoftware("filius.software.clientserver.ServerBaustein");
@@ -216,7 +266,7 @@ public class TerminalRun {
client = (ClientBaustein) network.system(CLIENT).holeSoftware("filius.software.clientserver.ClientBaustein"); client = (ClientBaustein) network.system(CLIENT).holeSoftware("filius.software.clientserver.ClientBaustein");
} }
network.start(); network.start();
if (client != null) { if (story.equals("netstat")) {
sleepUntil(500); sleepUntil(500);
event(CLIENT, "call", "connect 192.168.0.31:55555"); event(CLIENT, "call", "connect 192.168.0.31:55555");
client.setZielIPAdresse("192.168.0.31"); client.setZielIPAdresse("192.168.0.31");
@@ -225,6 +275,14 @@ public class TerminalRun {
} }
for (Object[] act : acts) { for (Object[] act : acts) {
sleepUntil((Long) act[0]); sleepUntil((Long) act[0]);
if (act[2].equals("connect")) {
// The echo client's window: connect to the echo server (ClientBaustein.verbinden).
event(CLIENT, "call", "connect 192.168.0.31:55555");
client.setZielIPAdresse("192.168.0.31");
client.setZielPort(55555);
client.verbinden();
continue;
}
if (act[2].equals("disconnect")) { if (act[2].equals("disconnect")) {
// The echo client's window: its connection closed (ClientBaustein.trennen). // The echo client's window: its connection closed (ClientBaustein.trennen).
event(CLIENT, "call", "disconnect"); event(CLIENT, "call", "disconnect");