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 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 17:01:39 +00:00
co-authored by Claude Opus 5.5
parent 8869c865b1
commit 67fb608412
9 changed files with 651 additions and 8 deletions
+105
View File
@@ -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 <scenario.json> <story> <milliseconds> <out.json>
//
// 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<Object[]> 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);
}
}