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:
+4
-3
File diff suppressed because one or more lines are too long
@@ -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)
|
||||||
|
|||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user