From 67fb608412815fe33072615a04470e0b210b1755 Mon Sep 17 00:00:00 2001 From: Julian Kiedaisch Date: Wed, 30 Sep 2026 17:01:39 +0000 Subject: [PATCH] test(java-oracle): ArpRun records ARP as Java FILIUS runs it Sends to addresses no card has on the new arp-routed network (ping on the LAN and behind a router, two UDP datagrams, one behind a failing one): frames and calls. ScenarioNetwork builds routers and their interfaces. Measured: ARP asks twice, a round trip each, then host unreachable; the thread that waits is the ping's caller, the router's IP thread, or the node's transport thread per protocol, which blocks the next send. Co-Authored-By: Claude Opus 5.5 --- .../test/scenarios/arp-java-pingLan.java.json | 51 +++++++ .../scenarios/arp-java-pingRouted.java.json | 92 ++++++++++++ .../scenarios/arp-java-udpBehind.java.json | 141 ++++++++++++++++++ .../scenarios/arp-java-udpTwice.java.json | 91 +++++++++++ .../test/scenarios/arp-routed.scenario.json | 111 ++++++++++++++ tools/java-oracle/README.md | 26 ++++ tools/java-oracle/run.sh | 12 +- tools/java-oracle/src/ArpRun.java | 105 +++++++++++++ tools/java-oracle/src/ScenarioNetwork.java | 30 +++- 9 files changed, 651 insertions(+), 8 deletions(-) create mode 100644 packages/core/test/scenarios/arp-java-pingLan.java.json create mode 100644 packages/core/test/scenarios/arp-java-pingRouted.java.json create mode 100644 packages/core/test/scenarios/arp-java-udpBehind.java.json create mode 100644 packages/core/test/scenarios/arp-java-udpTwice.java.json create mode 100644 packages/core/test/scenarios/arp-routed.scenario.json create mode 100644 tools/java-oracle/src/ArpRun.java diff --git a/packages/core/test/scenarios/arp-java-pingLan.java.json b/packages/core/test/scenarios/arp-java-pingLan.java.json new file mode 100644 index 0000000..33c4e9a --- /dev/null +++ b/packages/core/test/scenarios/arp-java-pingLan.java.json @@ -0,0 +1,51 @@ +{ + "frames": { + "00000000-0000-0000-0000-000000000301": [ + { + "time": 716, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 3467, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + } + ], + "00000000-0000-0000-0000-000000000302": [ + { + "time": 837, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 3578, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + } + ], + "00000000-0000-0000-0000-000000000303": [] + }, + "events": [ + [ + 712, + "sendProbe 10.0.0.9 called" + ], + [ + 6222, + "sendProbe 10.0.0.9 returned ICMP 3/1 from 127.0.0.1" + ] + ] +} diff --git a/packages/core/test/scenarios/arp-java-pingRouted.java.json b/packages/core/test/scenarios/arp-java-pingRouted.java.json new file mode 100644 index 0000000..b43f3ff --- /dev/null +++ b/packages/core/test/scenarios/arp-java-pingRouted.java.json @@ -0,0 +1,92 @@ +{ + "frames": { + "00000000-0000-0000-0000-000000000301": [ + { + "time": 720, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.1" + }, + { + "time": 955, + "source": "02:00:00:00:03:21", + "destination": "02:00:00:00:03:11", + "arp": 2, + "senderIp": "10.0.0.1", + "targetIp": "10.0.0.2" + }, + { + "time": 959, + "source": "02:00:00:00:03:11", + "destination": "02:00:00:00:03:21", + "icmp": 8 + }, + { + "time": 6685, + "source": "02:00:00:00:03:21", + "destination": "02:00:00:00:03:11", + "icmp": 3 + } + ], + "00000000-0000-0000-0000-000000000302": [ + { + "time": 841, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.1" + }, + { + "time": 843, + "source": "02:00:00:00:03:21", + "destination": "02:00:00:00:03:11", + "arp": 2, + "senderIp": "10.0.0.1", + "targetIp": "10.0.0.2" + }, + { + "time": 1072, + "source": "02:00:00:00:03:11", + "destination": "02:00:00:00:03:21", + "icmp": 8 + }, + { + "time": 6574, + "source": "02:00:00:00:03:21", + "destination": "02:00:00:00:03:11", + "icmp": 3 + } + ], + "00000000-0000-0000-0000-000000000303": [ + { + "time": 1128, + "source": "02:00:00:00:03:22", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "20.0.0.1", + "targetIp": "20.0.0.9" + }, + { + "time": 3878, + "source": "02:00:00:00:03:22", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "20.0.0.1", + "targetIp": "20.0.0.9" + } + ] + }, + "events": [ + [ + 716, + "sendProbe 20.0.0.9 called" + ], + [ + 3709, + "sendProbe 20.0.0.9 returned null" + ] + ] +} diff --git a/packages/core/test/scenarios/arp-java-udpBehind.java.json b/packages/core/test/scenarios/arp-java-udpBehind.java.json new file mode 100644 index 0000000..2fd517f --- /dev/null +++ b/packages/core/test/scenarios/arp-java-udpBehind.java.json @@ -0,0 +1,141 @@ +{ + "frames": { + "00000000-0000-0000-0000-000000000301": [ + { + "time": 724, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 3475, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 6230, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.1" + }, + { + "time": 6453, + "source": "02:00:00:00:03:21", + "destination": "02:00:00:00:03:11", + "arp": 2, + "senderIp": "10.0.0.1", + "targetIp": "10.0.0.2" + }, + { + "time": 6453, + "source": "02:00:00:00:03:11", + "destination": "02:00:00:00:03:21", + "sourceIp": "10.0.0.2", + "destinationIp": "20.0.0.2", + "protocol": 17, + "sourcePort": 64055, + "destinationPort": 9, + "payload": "dahinter" + } + ], + "00000000-0000-0000-0000-000000000302": [ + { + "time": 846, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 3586, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 6341, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.1" + }, + { + "time": 6342, + "source": "02:00:00:00:03:21", + "destination": "02:00:00:00:03:11", + "arp": 2, + "senderIp": "10.0.0.1", + "targetIp": "10.0.0.2" + }, + { + "time": 6566, + "source": "02:00:00:00:03:11", + "destination": "02:00:00:00:03:21", + "sourceIp": "10.0.0.2", + "destinationIp": "20.0.0.2", + "protocol": 17, + "sourcePort": 64055, + "destinationPort": 9, + "payload": "dahinter" + } + ], + "00000000-0000-0000-0000-000000000303": [ + { + "time": 6622, + "source": "02:00:00:00:03:22", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "20.0.0.1", + "targetIp": "20.0.0.2" + }, + { + "time": 6622, + "source": "02:00:00:00:03:13", + "destination": "02:00:00:00:03:22", + "arp": 2, + "senderIp": "20.0.0.2", + "targetIp": "20.0.0.1" + }, + { + "time": 6733, + "source": "02:00:00:00:03:22", + "destination": "02:00:00:00:03:13", + "sourceIp": "10.0.0.2", + "destinationIp": "20.0.0.2", + "protocol": 17, + "sourcePort": 64055, + "destinationPort": 9, + "payload": "dahinter" + } + ] + }, + "events": [ + [ + 716, + "senden verloren called" + ], + [ + 720, + "senden verloren returned " + ], + [ + 720, + "senden dahinter called" + ], + [ + 6230, + "senden dahinter returned " + ] + ] +} diff --git a/packages/core/test/scenarios/arp-java-udpTwice.java.json b/packages/core/test/scenarios/arp-java-udpTwice.java.json new file mode 100644 index 0000000..fe1e6c3 --- /dev/null +++ b/packages/core/test/scenarios/arp-java-udpTwice.java.json @@ -0,0 +1,91 @@ +{ + "frames": { + "00000000-0000-0000-0000-000000000301": [ + { + "time": 722, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 3473, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 6228, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 8978, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + } + ], + "00000000-0000-0000-0000-000000000302": [ + { + "time": 844, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 3584, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 6339, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + }, + { + "time": 9090, + "source": "02:00:00:00:03:11", + "destination": "FF:FF:FF:FF:FF:FF", + "arp": 1, + "senderIp": "10.0.0.2", + "targetIp": "10.0.0.9" + } + ], + "00000000-0000-0000-0000-000000000303": [] + }, + "events": [ + [ + 713, + "senden eins called" + ], + [ + 718, + "senden eins returned " + ], + [ + 718, + "senden zwei called" + ], + [ + 6228, + "senden zwei returned " + ] + ] +} diff --git a/packages/core/test/scenarios/arp-routed.scenario.json b/packages/core/test/scenarios/arp-routed.scenario.json new file mode 100644 index 0000000..257351f --- /dev/null +++ b/packages/core/test/scenarios/arp-routed.scenario.json @@ -0,0 +1,111 @@ +{ + "seed": 61, + "nodes": [ + { + "id": "00000000-0000-0000-0000-000000000301", + "kind": "pc", + "ports": [ + { + "id": "00000000-0000-0000-0000-000000000311", + "label": "eth0" + } + ] + }, + { + "id": "00000000-0000-0000-0000-000000000302", + "kind": "router", + "ports": [ + { + "id": "00000000-0000-0000-0000-000000000321", + "label": "eth0" + }, + { + "id": "00000000-0000-0000-0000-000000000322", + "label": "eth1" + } + ] + }, + { + "id": "00000000-0000-0000-0000-000000000303", + "kind": "pc", + "ports": [ + { + "id": "00000000-0000-0000-0000-000000000313", + "label": "eth0" + } + ] + }, + { + "id": "00000000-0000-0000-0000-000000000304", + "kind": "networkSwitch", + "ports": [ + { + "id": "00000000-0000-0000-0000-000000000341", + "label": "sw1" + }, + { + "id": "00000000-0000-0000-0000-000000000342", + "label": "sw2" + }, + { + "id": "00000000-0000-0000-0000-000000000343", + "label": "sw3" + } + ] + } + ], + "links": [ + { + "id": "00000000-0000-0000-0000-00000000F301", + "sourceNode": "00000000-0000-0000-0000-000000000301", + "sourcePort": "00000000-0000-0000-0000-000000000311", + "targetNode": "00000000-0000-0000-0000-000000000304", + "targetPort": "00000000-0000-0000-0000-000000000341" + }, + { + "id": "00000000-0000-0000-0000-00000000F302", + "sourceNode": "00000000-0000-0000-0000-000000000302", + "sourcePort": "00000000-0000-0000-0000-000000000321", + "targetNode": "00000000-0000-0000-0000-000000000304", + "targetPort": "00000000-0000-0000-0000-000000000342" + }, + { + "id": "00000000-0000-0000-0000-00000000F303", + "sourceNode": "00000000-0000-0000-0000-000000000302", + "sourcePort": "00000000-0000-0000-0000-000000000322", + "targetNode": "00000000-0000-0000-0000-000000000303", + "targetPort": "00000000-0000-0000-0000-000000000313" + } + ], + "devices": { + "00000000-0000-0000-0000-000000000301": { + "ip": "10.0.0.2", + "mask": "255.255.255.0", + "gateway": "10.0.0.1" + }, + "00000000-0000-0000-0000-000000000302": { + "ip": "10.0.0.1", + "mask": "255.255.255.0" + }, + "00000000-0000-0000-0000-000000000303": { + "ip": "20.0.0.2", + "mask": "255.255.255.0", + "gateway": "20.0.0.1" + } + }, + "interfaces": [ + { + "node": "00000000-0000-0000-0000-000000000302", + "port": "00000000-0000-0000-0000-000000000321", + "ip": "10.0.0.1", + "mask": "255.255.255.0" + }, + { + "node": "00000000-0000-0000-0000-000000000302", + "port": "00000000-0000-0000-0000-000000000322", + "ip": "20.0.0.1", + "mask": "255.255.255.0" + } + ], + "steps": [] +} \ No newline at end of file diff --git a/tools/java-oracle/README.md b/tools/java-oracle/README.md index 9544c76..efd0413 100644 --- a/tools/java-oracle/README.md +++ b/tools/java-oracle/README.md @@ -247,3 +247,29 @@ unbekannten Namen (leere Antwort), und ein Verweis steht mit NS und A des Nameservers im Antwortteil. Der Resolver folgt dem Verweis selbst (iterativ) und wartet je Anfrage 5 RTT (13750 ms bei 90 %); ohne Server fragt ARP zweimal im Abstand einer RTT, dann scheitert der Aufruf. + +## ARP zur Laufzeit + +`ArpRun` sendet auf dem Netz von `arp-routed` (10.0.0.2 über einen Switch am +Router 10.0.0.1, 20.0.0.2 am Router 20.0.0.1; `ScenarioNetwork` baut dazu +Router mit `setzeAnzahlAnschluesse` und den `interfaces` des Szenarios) an +Adressen, die keine Karte hat, und schreibt nach `arp-java-.java.json` +die Frames und die Aufrufe. Abläufe: `pingLan` (Ping an 10.0.0.9, +`ICMP.sendProbe`), `pingRouted` (Ping an 20.0.0.9), `udpTwice` (zwei +Datagramme an 10.0.0.9), `udpBehind` (eines an 10.0.0.9, dann eines an +20.0.0.2). + +Gemessen (30.09.2026), was der Kern (nach Swift) anders macht: + +* ARP fragt bis zu zweimal und wartet je eine RTT (`holeARPTabellenEintrag(ziel, + 2, …)`); dann „Host unreachable" (ICMP 3/1). Einen Negativ-Cache gibt es + nicht: das nächste Paket fragt wieder zweimal. +* Wer wartet, ist ein Thread: beim Ping der Aufrufer (5,5 s, dann ICMP 3/1 + vom eigenen Rechner, Absender 127.0.0.1), beim Weiterleiten der IP-Thread + des Routers, bei UDP und TCP der Transport-Thread je Protokoll und Knoten. + `senden` kehrt sofort zurück, aber der Transport-Thread hält seine + Warteschlange gesperrt, solange er wartet: das nächste `senden` blockiert + bis dahin (5,5 s), und ein Datagramm an einen erreichbaren Rechner geht + erst danach. +* Ein Ping wartet eine RTT auf die Antwort; das „Host unreachable" des + Routers (nach dessen 5,5 s) kommt zu spät. diff --git a/tools/java-oracle/run.sh b/tools/java-oracle/run.sh index 3068f16..d44b38b 100755 --- a/tools/java-oracle/run.sh +++ b/tools/java-oracle/run.sh @@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then fi classpath="$upstream/target/filius.jar:$upstream/target/lib/*" 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" +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" # FILIUS writes its settings below $HOME; keep them out of the real one. mkdir -p "$here/.build/home" # Removed first: a crashed run must not leave the previous result standing. @@ -141,3 +141,13 @@ for run in address:5000 mail:5000 referral:5000 unknown:5000 noServer:22000; do "$root/packages/core/test/scenarios/dns-lan.scenario.json" "$story" "${run##*:}" "$output" 2>&1 | grep -v 'logback\|^$' || true test -s "$output" done + +# ARP as Java FILIUS runs it: sends to addresses no card has on arp-routed's +# network (ArpRun); frames and the calls, for the core's ARP comparison. +for story in pingLan pingRouted udpTwice udpBehind; do + output="$root/packages/core/test/scenarios/arp-java-$story.java.json" + rm -f "$output" + HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" ArpRun \ + "$root/packages/core/test/scenarios/arp-routed.scenario.json" "$story" 16000 "$output" 2>&1 | grep -v 'logback\|^$' || true + test -s "$output" +done diff --git a/tools/java-oracle/src/ArpRun.java b/tools/java-oracle/src/ArpRun.java new file mode 100644 index 0000000..2e3515a --- /dev/null +++ b/tools/java-oracle/src/ArpRun.java @@ -0,0 +1,105 @@ +// Runs a story of sends to addresses ARP cannot resolve on arp-routed's +// network in Java FILIUS (ScenarioNetwork) and records every frame each card +// saw and when each call started and ended. +// +// ArpRun +// +// 10.0.0.2 sits behind a switch on the router's 10.0.0.1; 20.0.0.2 on the +// router's 20.0.0.1. No card has 10.0.0.9 or 20.0.0.9. The stories: +// pingLan 10.0.0.2 pings 10.0.0.9 (ICMP.sendProbe, as the ping command) +// pingRouted 10.0.0.2 pings 20.0.0.9: the router's ARP fails +// udpTwice 10.0.0.2 sends two UDP datagrams to 10.0.0.9 at once +// udpBehind 10.0.0.2 sends a UDP datagram to 10.0.0.9, then one to 20.0.0.2 + +import filius.software.transportschicht.UDPSocket; +import filius.software.vermittlungsschicht.IcmpPaket; + +import java.util.ArrayList; +import java.util.List; + +public class ArpRun { + static final String HOST = "00000000-0000-0000-0000-000000000301"; + + static ScenarioNetwork network; + static final List events = new ArrayList<>(); + + interface Call { + String run() throws Exception; + } + + static synchronized void event(String what) { + events.add(new Object[] { System.currentTimeMillis() - network.start, what }); + } + + static void call(String label, Call call) { + event(label + " called"); + try { + event(label + " returned " + call.run()); + } catch (Exception e) { + event(label + " failed " + e.getClass().getSimpleName()); + } + } + + static String icmp(IcmpPaket packet) { + return packet == null ? "null" : "ICMP " + packet.getIcmpType() + "/" + packet.getIcmpCode() + " from " + packet.getSender(); + } + + static void send(String target, String payload) throws Exception { + UDPSocket socket = new UDPSocket(network.system(HOST), target, 9); + socket.verbinden(); + socket.senden(payload); + } + + 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 "pingLan": + call("sendProbe 10.0.0.9", () -> icmp(network.system(HOST).holeICMP().sendProbe("10.0.0.9", 64, 1, 1))); + break; + case "pingRouted": + call("sendProbe 20.0.0.9", () -> icmp(network.system(HOST).holeICMP().sendProbe("20.0.0.9", 64, 1, 1))); + break; + case "udpTwice": + call("senden eins", () -> { + send("10.0.0.9", "eins"); + return ""; + }); + call("senden zwei", () -> { + send("10.0.0.9", "zwei"); + return ""; + }); + break; + case "udpBehind": + call("senden verloren", () -> { + send("10.0.0.9", "verloren"); + return ""; + }); + call("senden dahinter", () -> { + send("20.0.0.2", "dahinter"); + return ""; + }); + 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 (ArpRun.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); + } +} diff --git a/tools/java-oracle/src/ScenarioNetwork.java b/tools/java-oracle/src/ScenarioNetwork.java index cea3126..1bb5667 100644 --- a/tools/java-oracle/src/ScenarioNetwork.java +++ b/tools/java-oracle/src/ScenarioNetwork.java @@ -16,6 +16,7 @@ import filius.hardware.knoten.LokalerKnoten; import filius.hardware.knoten.Notebook; import filius.hardware.knoten.Rechner; import filius.hardware.knoten.Switch; +import filius.hardware.knoten.Vermittlungsrechner; import filius.rahmenprogramm.nachrichten.Lauscher; import filius.software.dhcp.DHCPServer; import filius.software.dns.DNSServer; @@ -59,6 +60,7 @@ public class ScenarioNetwork { private void build(Map scenario) { Map ports = new HashMap<>(); + Map nicsByPort = new HashMap<>(); for (Object each : list(scenario.get("nodes"))) { Map node = map(each); String id = (String) node.get("id"); @@ -75,18 +77,25 @@ public class ScenarioNetwork { case "networkSwitch": knoten = new Switch(); break; + case "router": + knoten = new Vermittlungsrechner(); + ((InternetKnoten) knoten).setzeAnzahlAnschluesse(nodePorts.size()); + break; default: throw new IllegalArgumentException("node kind " + kind + " is not built by this tool"); } knoten.setName(id); if (knoten instanceof InternetKnoten) { - if (nodePorts.size() != 1) throw new IllegalArgumentException("a host has one port here"); - Map port = map(nodePorts.get(0)); - NetzwerkInterface nic = ((InternetKnoten) knoten).getNetzwerkInterfaces().get(0); - String mac = port.containsKey("mac") ? (String) port.get("mac") : stableMac((String) port.get("id")); - nic.setMac(mac); - ports.put((String) port.get("id"), nic.getPort()); - macByNode.put(id, mac); + List nics = ((InternetKnoten) knoten).getNetzwerkInterfaces(); + for (int index = 0; index < nodePorts.size(); index++) { + Map port = map(nodePorts.get(index)); + NetzwerkInterface nic = nics.get(index); + String mac = port.containsKey("mac") ? (String) port.get("mac") : stableMac((String) port.get("id")); + nic.setMac(mac); + ports.put((String) port.get("id"), nic.getPort()); + nicsByPort.put((String) port.get("id"), nic); + if (index == 0) macByNode.put(id, mac); + } } else { LinkedList switchPorts = ((LokalerKnoten) knoten).getAnschluesse(); for (int index = 0; index < nodePorts.size(); index++) { @@ -103,6 +112,13 @@ public class ScenarioNetwork { nic.setGateway((String) device.getOrDefault("gateway", "")); nic.setDns((String) device.getOrDefault("dns", "")); } + // A router's other cards: `interfaces` names them by port. + for (Object each : list(scenario.getOrDefault("interfaces", new ArrayList<>()))) { + Map item = map(each); + NetzwerkInterface nic = nicsByPort.get(item.get("port")); + nic.setIp((String) item.get("ip")); + nic.setSubnetzMaske((String) item.get("mask")); + } for (Object each : list(scenario.get("links"))) { Map link = map(each); Kabel cable = new Kabel();