test(core): RIP measured with Java FILIUS
RipRun runs rip-chain's three routers in Java FILIUS, each router's random set by reflection to the core's ripSeed, and writes the frames, every change of each RIP table and the calls. converge: the tables fill, a ping at 45 s. expire: R3 falls silent at 10 s; R2 marks its network 16 hops only at its own next beacon, R1 passes it on at once, the ping gets ICMP 3/0. Both match: per sending card the same messages at the same times, every table through the same states. The first 5 s are compared by the state the tables reach, since Java's routers start one after the other. Measured on the JDK: Java's pause divides nextFloat() by 3 in float; the core does too now (a millisecond off in one draw of about 10,000 before). Scenarios name their RIP routers in the new field rip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -344,3 +344,34 @@ Frames, Abstände und die Zeiten, zu denen die Aufrufe zurückkehren). Die UDP-A
|
||||
seit F2.4b-2: ein Senden kehrt sofort zurück wie in Java (Swifts Senden wartete, bis
|
||||
alle Frames angekommen waren), und je Knoten und Protokoll sendet ein Transport-Thread
|
||||
ein Segment nach dem anderen.
|
||||
|
||||
## RIP zur Laufzeit
|
||||
|
||||
`RipRun` startet das Netz von `rip-chain` (PC 10.0.1.2 – R1 – R2 – R3 – PC
|
||||
10.0.3.2, jeder Router mit RIP; `ScenarioNetwork` schaltet RIP für die im
|
||||
Feld `rip` genannten Router ein) und schreibt nach `rip-java-<ablauf>.java.json`
|
||||
die Frames, jede Änderung jeder RIP-Tabelle (alle 2 ms abgefragt, Zeile
|
||||
`netz/maske gateway schnittstelle hops; …`) und die Aufrufe. Java würfelt die
|
||||
Pausen eines Routers aus einem neuen `Random`; `RipRun` setzt jedem Router
|
||||
per Reflection (`VermittlungsrechnerBetriebssystem.ripbeacon`,
|
||||
`RIPBeacon.rand`) den Startwert, den der Kern ihm gibt (`ripSeed`: Startwert
|
||||
des Szenarios, `0x52495000` und FNV-1a der Router-ID). Dann würfeln beide
|
||||
dieselben Pausen.
|
||||
|
||||
* `converge`: die Tabellen füllen sich, bei 45 s pingt 10.0.1.2 10.0.3.2.
|
||||
* `expire`: bei 10 s schweigt R3 (`RIPTable.setNextBeacon(Long.MAX_VALUE)`),
|
||||
bei 125 s der Ping noch einmal.
|
||||
|
||||
Gemessen (30.09.2026, dazu die Pausenformel am JDK):
|
||||
|
||||
* Die Pause ist `(int) (30000 * (rand.nextFloat() / 3 + 0.84))`; `nextFloat() / 3`
|
||||
rechnet Java in `float`, in etwa einem von 10.000 Fällen eine Millisekunde
|
||||
anders als in `double`.
|
||||
* Eine gelernte Route bekommt 16 Hops erst beim nächsten Beacon ihres Routers
|
||||
nach dem Verfallen (`RIPTable.check`): R3s Netz verfiel bei R2 nach rund
|
||||
76,4 s, R2 setzte es bei seinem Beacon um 86,7 s auf 16; R1 hörte die 16 vom
|
||||
selben Router und meldete sofort. Der Ping bekommt dann ICMP 3/0 von R1.
|
||||
* Die Router starten nacheinander, rund 20 ms auseinander; wer einen
|
||||
Nachbarn vor seinem ersten Beacon hört, sendet die gelernte Route gleich
|
||||
mit. Senden mehrere zugleich, fehlt im Lauscher mancher eigene Frame.
|
||||
|
||||
|
||||
@@ -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" "$here/src/ArpRun.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"
|
||||
# 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.
|
||||
@@ -152,6 +152,17 @@ for run in address:5000 referral:5000 unknown:5000 unknownInZone:5000 unknownInO
|
||||
test -s "$output"
|
||||
done
|
||||
|
||||
# RIP as Java FILIUS runs it: rip-chain's routers, each with the core's seed
|
||||
# for its pauses (RipRun); frames, table changes and the ping. Real time.
|
||||
for run in converge:60000 expire:135000; do
|
||||
story="${run%%:*}"
|
||||
output="$root/packages/core/test/scenarios/rip-java-$story.java.json"
|
||||
rm -f "$output"
|
||||
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" RipRun \
|
||||
"$root/packages/core/test/scenarios/rip-chain.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 routedUdpBehind routedPingBesideUdp routedPingBehindPing; do
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
// Runs rip-chain's network in Java FILIUS (ScenarioNetwork) and records what
|
||||
// RIP really does: every frame each card saw, every change of each router's
|
||||
// RIP table, and the calls of the story.
|
||||
//
|
||||
// RipRun <scenario.json> <story> <milliseconds> <out.json>
|
||||
//
|
||||
// PC 10.0.1.2 - R1 (10.0.1.1, 10.0.12.1) - R2 (10.0.12.2, 10.0.23.2) -
|
||||
// R3 (10.0.23.3, 10.0.3.1) - PC 10.0.3.2, each router with RIP. Java draws a
|
||||
// router's pauses from a new Random; this tool gives each the seed the core
|
||||
// gives it (ripSeed: the scenario's seed, 0x52495000 and the FNV-1a hash of
|
||||
// the router's id). The stories:
|
||||
// converge the tables fill; at 45 s 10.0.1.2 pings 10.0.3.2
|
||||
// expire at 10 s R3 falls silent (its next beacon never comes); its
|
||||
// network expires at R2, which tells R1; at 125 s the ping again
|
||||
|
||||
import filius.hardware.knoten.Knoten;
|
||||
import filius.software.rip.RIPBeacon;
|
||||
import filius.software.rip.RIPRoute;
|
||||
import filius.software.rip.RIPTable;
|
||||
import filius.software.system.VermittlungsrechnerBetriebssystem;
|
||||
import filius.software.vermittlungsschicht.IcmpPaket;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class RipRun {
|
||||
static final String PC_A = "00000000-0000-0000-0000-000000000401";
|
||||
static final String R3 = "00000000-0000-0000-0000-000000000404";
|
||||
|
||||
static ScenarioNetwork network;
|
||||
static final List<Object[]> events = new ArrayList<>();
|
||||
static final List<Object[]> tables = new ArrayList<>();
|
||||
static final Map<String, String> lastTables = new LinkedHashMap<>();
|
||||
|
||||
static synchronized void event(String what) {
|
||||
events.add(new Object[] { System.currentTimeMillis() - network.start, what });
|
||||
}
|
||||
|
||||
/** The table as one line: `network/mask gateway interface hops; …`, in its order. */
|
||||
static String tableText(RIPTable table) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
synchronized (table) {
|
||||
for (RIPRoute route : table.routes) {
|
||||
if (text.length() > 0) text.append("; ");
|
||||
text.append(route.getNetAddress()).append('/').append(route.getNetMask()).append(' ').append(route.getGateway())
|
||||
.append(' ').append(route.getInterfaceIpAddress()).append(' ').append(route.hops);
|
||||
}
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
static synchronized void pollTables() {
|
||||
for (Map.Entry<String, Knoten> entry : network.nodes.entrySet()) {
|
||||
if (!(entry.getValue().getSystemSoftware() instanceof VermittlungsrechnerBetriebssystem)) continue;
|
||||
RIPTable table = ((VermittlungsrechnerBetriebssystem) entry.getValue().getSystemSoftware()).getRIPTable();
|
||||
if (table == null) continue;
|
||||
String text = tableText(table);
|
||||
if (!text.equals(lastTables.get(entry.getKey()))) {
|
||||
lastTables.put(entry.getKey(), text);
|
||||
tables.add(new Object[] { System.currentTimeMillis() - network.start, entry.getKey(), text });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The core's ripSeed. */
|
||||
static long ripSeed(long seed, String nodeId) {
|
||||
int hash = 0x811c9dc5;
|
||||
for (int index = 0; index < nodeId.length(); index++) {
|
||||
hash = (hash ^ nodeId.charAt(index)) * 0x01000193;
|
||||
}
|
||||
return seed ^ 0x52495000L ^ (hash & 0xffffffffL);
|
||||
}
|
||||
|
||||
static String icmp(IcmpPaket packet) {
|
||||
return packet == null ? "null" : "ICMP " + packet.getIcmpType() + "/" + packet.getIcmpCode() + " from " + packet.getSender();
|
||||
}
|
||||
|
||||
static void ping(String target) {
|
||||
event("sendProbe " + target + " called");
|
||||
IcmpPaket answer = network.system(PC_A).holeICMP().sendProbe(target, 64, 1, 1);
|
||||
event("sendProbe " + target + " returned " + icmp(answer));
|
||||
}
|
||||
|
||||
static void sleepUntil(long milliseconds) throws InterruptedException {
|
||||
long rest = network.start + milliseconds - System.currentTimeMillis();
|
||||
if (rest > 0) Thread.sleep(rest);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
network = ScenarioNetwork.read(args[0]);
|
||||
String story = args[1];
|
||||
long end = Long.parseLong(args[2]);
|
||||
long seed = ScenarioNetwork.seed(args[0]);
|
||||
network.start();
|
||||
Field beaconField = VermittlungsrechnerBetriebssystem.class.getDeclaredField("ripbeacon");
|
||||
beaconField.setAccessible(true);
|
||||
Field randomField = RIPBeacon.class.getDeclaredField("rand");
|
||||
randomField.setAccessible(true);
|
||||
for (Map.Entry<String, Knoten> entry : network.nodes.entrySet()) {
|
||||
if (!(entry.getValue().getSystemSoftware() instanceof VermittlungsrechnerBetriebssystem)) continue;
|
||||
VermittlungsrechnerBetriebssystem os = (VermittlungsrechnerBetriebssystem) entry.getValue().getSystemSoftware();
|
||||
if (!os.isRipEnabled()) continue;
|
||||
randomField.set(beaconField.get(os), new Random(ripSeed(seed, entry.getKey())));
|
||||
}
|
||||
event("seeded");
|
||||
Thread poller = new Thread(() -> {
|
||||
while (true) {
|
||||
pollTables();
|
||||
try {
|
||||
Thread.sleep(2);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
poller.setDaemon(true);
|
||||
poller.start();
|
||||
|
||||
switch (story) {
|
||||
case "converge":
|
||||
sleepUntil(45_000);
|
||||
ping("10.0.3.2");
|
||||
break;
|
||||
case "expire":
|
||||
sleepUntil(10_000);
|
||||
((VermittlungsrechnerBetriebssystem) network.system(R3)).getRIPTable().setNextBeacon(Long.MAX_VALUE);
|
||||
event("R3 silent");
|
||||
sleepUntil(125_000);
|
||||
ping("10.0.3.2");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown story " + story);
|
||||
}
|
||||
sleepUntil(end);
|
||||
pollTables();
|
||||
|
||||
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
|
||||
out.open('{');
|
||||
network.writeFrames(out);
|
||||
synchronized (RipRun.class) {
|
||||
out.key("events").open('[');
|
||||
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).close(']');
|
||||
out.close(']');
|
||||
out.key("tables").open('[');
|
||||
for (Object[] each : tables) out.open('[').number((Long) each[0]).string((String) each[1]).string((String) each[2]).close(']');
|
||||
out.close(']');
|
||||
}
|
||||
out.close('}');
|
||||
network.stop();
|
||||
ScenarioNetwork.write(args[3], out);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import filius.software.dhcp.DHCPServer;
|
||||
import filius.software.dns.DNSServer;
|
||||
import filius.software.netzzugangsschicht.EthernetFrame;
|
||||
import filius.software.system.InternetKnotenBetriebssystem;
|
||||
import filius.software.system.VermittlungsrechnerBetriebssystem;
|
||||
import filius.software.transportschicht.Segment;
|
||||
import filius.software.transportschicht.TcpSegment;
|
||||
import filius.software.vermittlungsschicht.ArpPaket;
|
||||
@@ -58,6 +59,14 @@ public class ScenarioNetwork {
|
||||
return network;
|
||||
}
|
||||
|
||||
/** The scenario's seed. */
|
||||
@SuppressWarnings("unchecked")
|
||||
static long seed(String path) throws Exception {
|
||||
Map<String, Object> scenario = (Map<String, Object>) new JsonReader(
|
||||
new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8)).value();
|
||||
return ((Number) scenario.get("seed")).longValue();
|
||||
}
|
||||
|
||||
private void build(Map<String, Object> scenario) {
|
||||
Map<String, Port> ports = new HashMap<>();
|
||||
Map<String, NetzwerkInterface> nicsByPort = new HashMap<>();
|
||||
@@ -156,6 +165,9 @@ public class ScenarioNetwork {
|
||||
for (Object each : list(scenario.getOrDefault("dhcpClients", new ArrayList<>()))) {
|
||||
system(nodes.get((String) each)).setDHCPKonfiguration(true);
|
||||
}
|
||||
for (Object each : list(scenario.getOrDefault("rip", new ArrayList<>()))) {
|
||||
((VermittlungsrechnerBetriebssystem) system(nodes.get((String) each))).setRipEnabled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user