test(core): WLAN measured with Java FILIUS — a wireless cable is a cable, an SSID connects nothing

WlanRun pings on the new network wlan-lan: over wireless cables, over a
plain cable, and to and from a notebook on the switch's SSID without a
cable. The engine matches Java without a change: it runs the links only.
Swift's wirelessAssociations, which join a host to its SSID's switch at
run time, are not taken. ScenarioNetwork sets wireless cables, wireless
cards and SSIDs; a corpus test pings over the wireless cables of
multicast_server.fls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 08:04:18 +00:00
co-authored by Claude Opus 5.5
parent 7a77bc1b43
commit 65ccd9ddd7
14 changed files with 677 additions and 7 deletions
+4 -3
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -3,7 +3,11 @@
// "Runtime snapshots and transient state" (lines ~313–938) // "Runtime snapshots and transient state" (lines ~313–938)
// "Deterministic runtime engine" (lines ~984–1284) // "Deterministic runtime engine" (lines ~984–1284)
// "Ethernet, ARP, IPv4, and ICMP packet runtime" (lines ~1284–3585, // "Ethernet, ARP, IPv4, and ICMP packet runtime" (lines ~1284–3585,
// without remote link, wireless and gateway NAT; with the firewall) // without remote link and gateway NAT; with the firewall. Wireless
// needs nothing here: in Java a wireless cable is a cable like any
// other at run time and an SSID connects nothing — only the editor
// lays the cable by it; Swift's wirelessAssociations, which join by
// SSID at run time, are not taken. Measured with Java FILIUS)
// "UDP sockets and Router RIP" (lines ~4472–5187; RIP // "UDP sockets and Router RIP" (lines ~4472–5187; RIP
// after Java's filius.software.rip: expiry checked at the beacon, a // after Java's filius.software.rip: expiry checked at the beacon, a
// random of its own per router, any source port) // random of its own per router, any source port)
+4 -1
View File
@@ -22,11 +22,14 @@ export interface Scenario {
seed: number; seed: number;
speedPercent?: number; speedPercent?: number;
nodes: { id: string; kind: NodeKind; ports: { id: string; label: string; mac?: string }[] }[]; nodes: { id: string; kind: NodeKind; ports: { id: string; label: string; mac?: string }[] }[];
links: { id: string; sourceNode: string; sourcePort: string; targetNode: string; targetPort: string }[]; /** `wireless`: a wireless cable (Java only: Kabel.wireless; a cable like any other at run time). */
links: { id: string; sourceNode: string; sourcePort: string; targetNode: string; targetPort: string; wireless?: boolean }[];
devices?: Record<string, { ip: string; mask: string; gateway?: string; dns?: string }>; devices?: Record<string, { ip: string; mask: string; gateway?: string; dns?: string }>;
interfaces?: { node: string; port: string; ip: string; mask: string }[]; interfaces?: { node: string; port: string; ip: string; mask: string }[];
routes?: Record<string, { destination: string; mask: string; gateway: string; interface: string }[]>; routes?: Record<string, { destination: string; mask: string; gateway: string; interface: string }[]>;
switches?: Record<string, { ssid: string; retention?: number }>; switches?: Record<string, { ssid: string; retention?: number }>;
/** Hosts with a wireless card and the SSID they join (Java only: the editor lays the cable, the simulation does not ask). */
wireless?: Record<string, { ssid: string }>;
firewalls?: Record< firewalls?: Record<
string, string,
{ {
@@ -0,0 +1,35 @@
{
"frames": {
"00000000-0000-0000-0000-000000000601": [],
"00000000-0000-0000-0000-000000000602": [],
"00000000-0000-0000-0000-000000000603": [],
"00000000-0000-0000-0000-000000000605": [
{
"time": 717,
"source": "02:00:00:00:06:15",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.14",
"targetIp": "192.168.1.11"
},
{
"time": 3468,
"source": "02:00:00:00:06:15",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.14",
"targetIp": "192.168.1.11"
}
]
},
"events": [
[
713,
"sendProbe 192.168.1.11 called"
],
[
6225,
"sendProbe 192.168.1.11 returned ICMP 3/1 from 127.0.0.1"
]
]
}
@@ -0,0 +1,101 @@
{
"frames": {
"00000000-0000-0000-0000-000000000601": [
{
"time": 724,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.13"
},
{
"time": 960,
"source": "02:00:00:00:06:13",
"destination": "02:00:00:00:06:11",
"arp": 2,
"senderIp": "192.168.1.13",
"targetIp": "192.168.1.11"
},
{
"time": 964,
"source": "02:00:00:00:06:11",
"destination": "02:00:00:00:06:13",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.1.11",
"destinationIp": "192.168.1.13",
"sequence": 1
},
{
"time": 1189,
"source": "02:00:00:00:06:13",
"destination": "02:00:00:00:06:11",
"icmp": 0,
"icmpCode": 0,
"sourceIp": "192.168.1.13",
"destinationIp": "192.168.1.11",
"sequence": 1
}
],
"00000000-0000-0000-0000-000000000602": [
{
"time": 846,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.13"
}
],
"00000000-0000-0000-0000-000000000603": [
{
"time": 846,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.13"
},
{
"time": 848,
"source": "02:00:00:00:06:13",
"destination": "02:00:00:00:06:11",
"arp": 2,
"senderIp": "192.168.1.13",
"targetIp": "192.168.1.11"
},
{
"time": 1077,
"source": "02:00:00:00:06:11",
"destination": "02:00:00:00:06:13",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.1.11",
"destinationIp": "192.168.1.13",
"sequence": 1
},
{
"time": 1077,
"source": "02:00:00:00:06:13",
"destination": "02:00:00:00:06:11",
"icmp": 0,
"icmpCode": 0,
"sourceIp": "192.168.1.13",
"destinationIp": "192.168.1.11",
"sequence": 1
}
],
"00000000-0000-0000-0000-000000000605": []
},
"events": [
[
719,
"sendProbe 192.168.1.13 called"
],
[
1190,
"sendProbe 192.168.1.13 returned ICMP 0/0 from 192.168.1.13"
]
]
}
@@ -0,0 +1,101 @@
{
"frames": {
"00000000-0000-0000-0000-000000000601": [
{
"time": 720,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.12"
},
{
"time": 955,
"source": "02:00:00:00:06:12",
"destination": "02:00:00:00:06:11",
"arp": 2,
"senderIp": "192.168.1.12",
"targetIp": "192.168.1.11"
},
{
"time": 959,
"source": "02:00:00:00:06:11",
"destination": "02:00:00:00:06:12",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.1.11",
"destinationIp": "192.168.1.12",
"sequence": 1
},
{
"time": 1184,
"source": "02:00:00:00:06:12",
"destination": "02:00:00:00:06:11",
"icmp": 0,
"icmpCode": 0,
"sourceIp": "192.168.1.12",
"destinationIp": "192.168.1.11",
"sequence": 1
}
],
"00000000-0000-0000-0000-000000000602": [
{
"time": 842,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.12"
},
{
"time": 843,
"source": "02:00:00:00:06:12",
"destination": "02:00:00:00:06:11",
"arp": 2,
"senderIp": "192.168.1.12",
"targetIp": "192.168.1.11"
},
{
"time": 1072,
"source": "02:00:00:00:06:11",
"destination": "02:00:00:00:06:12",
"icmp": 8,
"icmpCode": 0,
"sourceIp": "192.168.1.11",
"destinationIp": "192.168.1.12",
"sequence": 1
},
{
"time": 1072,
"source": "02:00:00:00:06:12",
"destination": "02:00:00:00:06:11",
"icmp": 0,
"icmpCode": 0,
"sourceIp": "192.168.1.12",
"destinationIp": "192.168.1.11",
"sequence": 1
}
],
"00000000-0000-0000-0000-000000000603": [
{
"time": 842,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.12"
}
],
"00000000-0000-0000-0000-000000000605": []
},
"events": [
[
715,
"sendProbe 192.168.1.12 called"
],
[
1185,
"sendProbe 192.168.1.12 returned ICMP 0/0 from 192.168.1.12"
]
]
}
@@ -0,0 +1,69 @@
{
"frames": {
"00000000-0000-0000-0000-000000000601": [
{
"time": 716,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.14"
},
{
"time": 3466,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.14"
}
],
"00000000-0000-0000-0000-000000000602": [
{
"time": 837,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.14"
},
{
"time": 3578,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.14"
}
],
"00000000-0000-0000-0000-000000000603": [
{
"time": 837,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.14"
},
{
"time": 3578,
"source": "02:00:00:00:06:11",
"destination": "FF:FF:FF:FF:FF:FF",
"arp": 1,
"senderIp": "192.168.1.11",
"targetIp": "192.168.1.14"
}
],
"00000000-0000-0000-0000-000000000605": []
},
"events": [
[
711,
"sendProbe 192.168.1.14 called"
],
[
6222,
"sendProbe 192.168.1.14 returned ICMP 3/1 from 127.0.0.1"
]
]
}
@@ -0,0 +1,127 @@
{
"seed": 71,
"nodes": [
{
"id": "00000000-0000-0000-0000-000000000601",
"kind": "notebook",
"ports": [
{
"id": "00000000-0000-0000-0000-000000000611",
"label": "eth0"
}
]
},
{
"id": "00000000-0000-0000-0000-000000000602",
"kind": "notebook",
"ports": [
{
"id": "00000000-0000-0000-0000-000000000612",
"label": "eth0"
}
]
},
{
"id": "00000000-0000-0000-0000-000000000603",
"kind": "pc",
"ports": [
{
"id": "00000000-0000-0000-0000-000000000613",
"label": "eth0"
}
]
},
{
"id": "00000000-0000-0000-0000-000000000604",
"kind": "networkSwitch",
"ports": [
{
"id": "00000000-0000-0000-0000-000000000641",
"label": "sw1"
},
{
"id": "00000000-0000-0000-0000-000000000642",
"label": "sw2"
},
{
"id": "00000000-0000-0000-0000-000000000643",
"label": "sw3"
},
{
"id": "00000000-0000-0000-0000-000000000644",
"label": "sw4"
}
]
},
{
"id": "00000000-0000-0000-0000-000000000605",
"kind": "notebook",
"ports": [
{
"id": "00000000-0000-0000-0000-000000000615",
"label": "eth0"
}
]
}
],
"links": [
{
"id": "00000000-0000-0000-0000-00000000F601",
"sourceNode": "00000000-0000-0000-0000-000000000601",
"sourcePort": "00000000-0000-0000-0000-000000000611",
"targetNode": "00000000-0000-0000-0000-000000000604",
"targetPort": "00000000-0000-0000-0000-000000000641",
"wireless": true
},
{
"id": "00000000-0000-0000-0000-00000000F602",
"sourceNode": "00000000-0000-0000-0000-000000000602",
"sourcePort": "00000000-0000-0000-0000-000000000612",
"targetNode": "00000000-0000-0000-0000-000000000604",
"targetPort": "00000000-0000-0000-0000-000000000642",
"wireless": true
},
{
"id": "00000000-0000-0000-0000-00000000F603",
"sourceNode": "00000000-0000-0000-0000-000000000603",
"sourcePort": "00000000-0000-0000-0000-000000000613",
"targetNode": "00000000-0000-0000-0000-000000000604",
"targetPort": "00000000-0000-0000-0000-000000000643"
}
],
"devices": {
"00000000-0000-0000-0000-000000000601": {
"ip": "192.168.1.11",
"mask": "255.255.255.0"
},
"00000000-0000-0000-0000-000000000602": {
"ip": "192.168.1.12",
"mask": "255.255.255.0"
},
"00000000-0000-0000-0000-000000000603": {
"ip": "192.168.1.13",
"mask": "255.255.255.0"
},
"00000000-0000-0000-0000-000000000605": {
"ip": "192.168.1.14",
"mask": "255.255.255.0"
}
},
"switches": {
"00000000-0000-0000-0000-000000000604": {
"ssid": "schule"
}
},
"wireless": {
"00000000-0000-0000-0000-000000000601": {
"ssid": "schule"
},
"00000000-0000-0000-0000-000000000602": {
"ssid": "schule"
},
"00000000-0000-0000-0000-000000000605": {
"ssid": "schule"
}
},
"steps": []
}
+81
View File
@@ -0,0 +1,81 @@
// WLAN as Java FILIUS runs it. tools/java-oracle's WlanRun pinged on
// wlan-lan's network in Java FILIUS — notebooks with wireless cards joined to
// the switch's SSID by wireless cables, a PC on a plain cable, a notebook on
// the same SSID without a cable — and wrote every frame each card saw and
// when each call started and ended (wlan-java-<story>.java.json). In Java a
// wireless cable is a cable like any other at run time, and an SSID connects
// nothing: only the editor reads it, to lay or remove the wireless cable
// (JHostKonfiguration.updateWifiConnection). The engine runs the scenario's
// links and knows no SSID; it makes the same calls: the frames must come in
// the same order, with the same content, at about the same distance from
// each other (javaRun.ts); the calls must end at about the same times and
// return the same. Swift instead joins a host to the switch with its SSID at
// run time, also without a cable (wirelessAssociations); not taken.
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import type { Id, NetworkRuntimeEngine } from '../src/index.js';
import { GAP_TOLERANCE_MILLISECONDS, gaps, javaFrames, watchedEngine, type JavaFrame } from './javaRun.js';
import type { Scenario } from './scenario.js';
const scenarioDirectory = join(dirname(fileURLToPath(import.meta.url)), 'scenarios');
const NOTEBOOK = '00000000-0000-0000-0000-000000000601';
const UNCABLED = '00000000-0000-0000-0000-000000000605';
interface JavaRun {
frames: Record<string, JavaFrame[]>;
events: [number, string][];
}
/** A ping in Java's words: the ICMP answer's type and code, or null when none came in time. */
function ping(engine: NetworkRuntimeEngine, from: Id, target: string): string {
const result = engine.sendIcmpEcho(from, target);
if (result.type === 'icmpError' && result.kind === 'destinationHostUnreachable') return 'ICMP 3/1';
const answered = (engine.state.icmpObservationsByNodeId.get(from) ?? []).some((observation) => observation.message.kind === 'echoReply');
return answered ? 'ICMP 0/0' : 'null';
}
const STORIES: Record<string, [Id, string]> = {
pingWired: [NOTEBOOK, '192.168.1.13'],
pingWireless: [NOTEBOOK, '192.168.1.12'],
pingWithoutCable: [NOTEBOOK, '192.168.1.14'],
pingFromWithoutCable: [UNCABLED, '192.168.1.11'],
};
describe('WLAN as Java FILIUS runs it', () => {
const scenario = JSON.parse(readFileSync(join(scenarioDirectory, 'wlan-lan.scenario.json'), 'utf8')) as Scenario;
it('has wireless cables and an SSID without a cable to compare', () => {
expect(scenario.links.filter((link) => link.wireless === true)).toHaveLength(2);
const cabled = new Set(scenario.links.flatMap((link) => [link.sourceNode, link.targetNode]));
expect(cabled.has(UNCABLED)).toBe(false);
expect(scenario.wireless?.[UNCABLED]?.ssid).toBe(Object.values(scenario.switches ?? {})[0]?.ssid);
});
for (const [name, [from, target]] of Object.entries(STORIES)) {
it(name, () => {
const java = JSON.parse(readFileSync(join(scenarioDirectory, `wlan-java-${name}.java.json`), 'utf8')) as JavaRun;
const { engine, frames } = watchedEngine(scenario);
engine.handle({ type: 'advance', toMilliseconds: 500 });
const events: [number, string][] = [];
const label = `sendProbe ${target}`;
events.push([engine.state.currentTimeMilliseconds, `${label} called`]);
const result = ping(engine, from, target);
events.push([engine.state.currentTimeMilliseconds, `${label} returned ${result}`]);
engine.handle({ type: 'advance', toMilliseconds: 12_000 });
expect(events.map(([, event]) => event)).toEqual(java.events.map(([, event]) => event.replace(/ from .*$/, '')));
expect(Math.abs(events[1]![0] - events[0]![0] - (java.events[1]![0] - java.events[0]![0])), 'duration of the ping').toBeLessThanOrEqual(
GAP_TOLERANCE_MILLISECONDS,
);
const expected = gaps(javaFrames(java.frames));
const actual = gaps(frames());
for (let index = 0; index < Math.max(expected.length, actual.length); index += 1) {
expect(actual[index]?.text, `frame #${index + 1}`).toBe(expected[index]?.text);
expect(Math.abs(actual[index]!.gap - expected[index]!.gap), `gap before frame #${index + 1}`).toBeLessThanOrEqual(GAP_TOLERANCE_MILLISECONDS);
}
});
}
});
+10
View File
@@ -490,6 +490,16 @@ describe('an imported file runs in the simulation core', () => {
expect(engine.sendIcmpEcho(byName('Notebook 1.11'), '141.99.1.10').type).toBe('delivered'); expect(engine.sendIcmpEcho(byName('Notebook 1.11'), '141.99.1.10').type).toBe('delivered');
}); });
it('pings over wireless cables, a cable like any other at run time (Java Kabel.wireless)', () => {
const project = importFile(join(corpusRoot, 'java-examples/multicast_server.fls'));
const byIp = (ip: string) => project.graph.nodes.find((node) => project.deviceConfigurations.get(node.id)?.ipAddress === ip)!.id;
const notebook = byIp('192.168.0.10');
expect(project.hostWirelessConfigurations.get(notebook)?.isEnabled).toBe(true);
expect(project.graph.links).toHaveLength(3);
expect(startedEngine(project).sendIcmpEcho(notebook, '192.168.0.20').type).toBe('delivered');
});
it('pings across three routers on manual routes and the routers\' default gateways', () => { it('pings across three routers on manual routes and the routers\' default gateways', () => {
// Router 1 (141.99.1/2) sends everything else to 2.2; router 2 has manual // Router 1 (141.99.1/2) sends everything else to 2.2; router 2 has manual
// routes to 1.0 and 5.0; router 3 (141.99.4/5) sends everything else to // routes to 1.0 and 5.0; router 3 (141.99.4/5) sends everything else to
+29
View File
@@ -417,3 +417,32 @@ Threads zwischen Karte und Ethernet-Schicht sitzen):
(bei 90 % 55 Minuten), geprüft jede Sekunde, aufgefrischt nur von (bei 90 % 55 Minuten), geprüft jede Sekunde, aufgefrischt nur von
eingehenden Paketen (aus dem Code, nicht gemessen). eingehenden Paketen (aus dem Code, nicht gemessen).
## WLAN zur Laufzeit
`WlanRun` startet das Netz von `wlan-lan` (Notebooks 192.168.1.11 und .12
mit Funkkarte, je über ein Funkkabel am Switch mit der SSID „schule";
PC 192.168.1.13 an einem Kabel; Notebook 192.168.1.14 mit Funkkarte und
SSID „schule", aber ohne Kabel) und schreibt nach `wlan-java-<ablauf>.java.json`
die Frames und die Aufrufe. `ScenarioNetwork` setzt dazu `Kabel.setWireless`
nach dem Feld `wireless` eines Kabels, die SSID des Switches (`switches`)
und Funkkarte und SSID der Rechner (`wireless`). Abläufe: `pingWired` (.11
pingt .13), `pingWireless` (.11 pingt .12), `pingWithoutCable` (.11 pingt
.14), `pingFromWithoutCable` (.14 pingt .11).
Gemessen (01.10.2026), wie im Code (`Kabel.wireless`, `Betriebssystem.ssid`
liest nur die Oberfläche):
* Ein Funkkabel ist zur Laufzeit ein Kabel wie jedes andere: dieselben
Frames zu denselben Zeiten über Funk wie über Kabel (Ping 470 ms).
* Eine SSID verbindet nichts. Das Notebook ohne Kabel bleibt unerreichbar
(zweimal ARP, nach 5,5 s „Host unreachable" beim Absender), und es selbst
erreicht niemanden. Das Funkkabel legt nur Javas Editor
(`JHostKonfiguration.updateWifiConnection`: das alte entfernen, an den
Switch mit der SSID anschließen); ändert sich die SSID eines Switches,
entfernt er dessen Funkkabel, ohne neu zu verbinden. So entsteht in einer
Datei ein Rechner mit SSID ohne Kabel.
Swift verbindet zur Laufzeit jeden Rechner mit eingeschaltetem WLAN mit
einem freien Port des Switches seiner SSID, auch ohne Kabel
(`wirelessAssociations`). Der Kern folgt Java: Er kennt nur die Kabel.
+11 -1
View File
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
fi fi
classpath="$upstream/target/filius.jar:$upstream/target/lib/*" classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
mkdir -p "$here/.build" mkdir -p "$here/.build"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java"
# FILIUS writes its settings below $HOME; keep them out of the real one. # FILIUS writes its settings below $HOME; keep them out of the real one.
mkdir -p "$here/.build/home" mkdir -p "$here/.build/home"
# Removed first: a crashed run must not leave the previous result standing. # Removed first: a crashed run must not leave the previous result standing.
@@ -183,3 +183,13 @@ for story in pingLan pingRouted udpTwice udpBehind routedUdpBehind routedPingBes
"$root/packages/core/test/scenarios/arp-routed.scenario.json" "$story" 16000 "$output" 2>&1 | grep -v 'logback\|^$' || true "$root/packages/core/test/scenarios/arp-routed.scenario.json" "$story" 16000 "$output" 2>&1 | grep -v 'logback\|^$' || true
test -s "$output" test -s "$output"
done done
# WLAN as Java FILIUS runs it: pings over wireless cables, a plain cable and
# to a host on the switch's SSID without a cable (WlanRun); frames and calls.
for story in pingWired pingWireless pingWithoutCable pingFromWithoutCable; do
output="$root/packages/core/test/scenarios/wlan-java-$story.java.json"
rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" WlanRun \
"$root/packages/core/test/scenarios/wlan-lan.scenario.json" "$story" 12000 "$output" 2>&1 | grep -v 'logback\|^$' || true
test -s "$output"
done
+15 -1
View File
@@ -1,5 +1,6 @@
// A core scenario's network (packages/core/test/scenarios/*.scenario.json: // A core scenario's network (packages/core/test/scenarios/*.scenario.json:
// nodes, links, devices, dhcpServers, dhcpClients) built in Java FILIUS the // nodes, links (wireless or not), devices, switches' and hosts' SSIDs,
// dhcpServers, dhcpClients, …) built in Java FILIUS the
// way the core reads it: a port's MAC is its `mac`, else the core's // way the core reads it: a port's MAC is its `mac`, else the core's
// stableGeneratedMacAddress. Started as the action mode starts it (every // stableGeneratedMacAddress. Started as the action mode starts it (every
// system software's starten()); afterwards every frame each network card saw // system software's starten()); afterwards every frame each network card saw
@@ -22,8 +23,10 @@ import filius.rahmenprogramm.nachrichten.Lauscher;
import filius.software.dhcp.DHCPServer; import filius.software.dhcp.DHCPServer;
import filius.software.dns.DNSServer; import filius.software.dns.DNSServer;
import filius.software.netzzugangsschicht.EthernetFrame; import filius.software.netzzugangsschicht.EthernetFrame;
import filius.software.system.Betriebssystem;
import filius.software.system.GatewayFirmware; import filius.software.system.GatewayFirmware;
import filius.software.system.InternetKnotenBetriebssystem; import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.system.SwitchFirmware;
import filius.software.system.VermittlungsrechnerBetriebssystem; import filius.software.system.VermittlungsrechnerBetriebssystem;
import filius.software.transportschicht.Segment; import filius.software.transportschicht.Segment;
import filius.software.transportschicht.TcpSegment; import filius.software.transportschicht.TcpSegment;
@@ -139,6 +142,17 @@ public class ScenarioNetwork {
Map<String, Object> link = map(each); Map<String, Object> link = map(each);
Kabel cable = new Kabel(); Kabel cable = new Kabel();
cable.setAnschluesse(new Port[] { ports.get(link.get("sourcePort")), ports.get(link.get("targetPort")) }); cable.setAnschluesse(new Port[] { ports.get(link.get("sourcePort")), ports.get(link.get("targetPort")) });
cable.setWireless(Boolean.TRUE.equals(link.get("wireless")));
}
// A switch's SSID, a host's wireless card and the SSID it joins: what the
// editor reads to lay a wireless cable; the simulation does not ask.
for (Map.Entry<String, Object> entry : map(scenario.getOrDefault("switches", new HashMap<>())).entrySet()) {
((SwitchFirmware) nodes.get(entry.getKey()).getSystemSoftware()).setSSID((String) map(entry.getValue()).get("ssid"));
}
for (Map.Entry<String, Object> entry : map(scenario.getOrDefault("wireless", new HashMap<>())).entrySet()) {
Betriebssystem os = (Betriebssystem) system(nodes.get(entry.getKey()));
os.nicWireless(true);
os.setSsid((String) map(entry.getValue()).get("ssid"));
} }
for (Map.Entry<String, Object> entry : map(scenario.getOrDefault("dhcpServers", new HashMap<>())).entrySet()) { for (Map.Entry<String, Object> entry : map(scenario.getOrDefault("dhcpServers", new HashMap<>())).entrySet()) {
Map<String, Object> config = map(entry.getValue()); Map<String, Object> config = map(entry.getValue());
+85
View File
@@ -0,0 +1,85 @@
// Runs wlan-lan's network in Java FILIUS (ScenarioNetwork) and records what a
// wireless connection does at run time: every frame each card saw and when
// each call started and ended.
//
// WlanRun <scenario.json> <story> <milliseconds> <out.json>
//
// Notebooks 192.168.1.11 and .12 with wireless cards on the switch's SSID
// "schule", each joined by a wireless cable; PC 192.168.1.13 on a plain
// cable; notebook 192.168.1.14 with a wireless card on "schule" but no cable
// (as after the switch's SSID changed: Java's editor removes the cable and
// does not lay it again). The stories:
// pingWired .11 pings .13
// pingWireless .11 pings .12
// pingWithoutCable .11 pings .14
// pingFromWithoutCable .14 pings .11
import filius.software.vermittlungsschicht.IcmpPaket;
import java.util.ArrayList;
import java.util.List;
public class WlanRun {
static final String NOTEBOOK = "00000000-0000-0000-0000-000000000601";
static final String UNCABLED = "00000000-0000-0000-0000-000000000605";
static ScenarioNetwork network;
static final List<Object[]> events = new ArrayList<>();
static synchronized void event(String what) {
events.add(new Object[] { System.currentTimeMillis() - network.start, what });
}
static String icmp(IcmpPaket packet) {
return packet == null ? "null" : "ICMP " + packet.getIcmpType() + "/" + packet.getIcmpCode() + " from " + packet.getSender();
}
static void ping(String from, String target) {
String label = "sendProbe " + target;
event(label + " called");
try {
event(label + " returned " + icmp(network.system(from).holeICMP().sendProbe(target, 64, 1, 1)));
} catch (Exception e) {
event(label + " failed " + e.getClass().getSimpleName());
}
}
public static void main(String[] args) throws Exception {
network = ScenarioNetwork.read(args[0]);
String story = args[1];
long end = Long.parseLong(args[2]);
network.start();
Thread.sleep(500);
switch (story) {
case "pingWired":
ping(NOTEBOOK, "192.168.1.13");
break;
case "pingWireless":
ping(NOTEBOOK, "192.168.1.12");
break;
case "pingWithoutCable":
ping(NOTEBOOK, "192.168.1.14");
break;
case "pingFromWithoutCable":
ping(UNCABLED, "192.168.1.11");
break;
default:
throw new IllegalArgumentException("unknown story " + story);
}
long rest = network.start + end - System.currentTimeMillis();
if (rest > 0) Thread.sleep(rest);
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
out.open('{');
network.writeFrames(out);
synchronized (WlanRun.class) {
out.key("events").open('[');
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).close(']');
out.close(']');
}
out.close('}');
network.stop();
ScenarioNetwork.write(args[3], out);
System.exit(0);
}
}