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:
@@ -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