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>
158 lines
6.5 KiB
Java
158 lines
6.5 KiB
Java
// 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);
|
|
}
|
|
}
|