Every packet that comes in goes through its node's IP thread (UDP, TCP) or ICMP thread (ICMP), as Java's IPThread and ICMPThread: deliver, answer an echo request or forward, one after the other; while one waits for ARP, the next wait behind it. A packet a node sends to itself is delivered at once, as Java's IP.senden does. The transport send threads and these are one mechanism now (handToProtocolThread). ArpRun has three stories through the router (routedUdpBehind, routedPingBesideUdp, routedPingBehindPing); the oracle writes ICMP frames with type, code, addresses and sequence number. All seven ArpRun stories match Java FILIUS. Measured and not taken: Java's ARP wakes one waiter with notify, not the one whose answer came; the core wakes all that wait for the address. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
5.8 KiB
Java
143 lines
5.8 KiB
Java
// 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
|
|
// routedUdpBehind 10.0.0.2 sends a UDP datagram to 20.0.0.9, then one
|
|
// to 20.0.0.2: the router forwards both
|
|
// routedPingBesideUdp a ping to 20.0.0.2 (the router learns it), a UDP
|
|
// datagram to 20.0.0.9, 100 ms later (the UDP thread
|
|
// sends first) a ping to 20.0.0.2 again: only
|
|
// one thread waits for ARP at a time. Two would meet
|
|
// ARP's notify, which wakes one waiter, not the one
|
|
// whose answer came (a Java bug the core does not take)
|
|
// routedPingBehindPing an echo request to 20.0.0.9 (sent, not waited for),
|
|
// then a ping to 20.0.0.2
|
|
|
|
import filius.software.transportschicht.UDPSocket;
|
|
import filius.software.vermittlungsschicht.ICMP;
|
|
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;
|
|
case "routedUdpBehind":
|
|
call("senden verloren", () -> {
|
|
send("20.0.0.9", "verloren");
|
|
return "";
|
|
});
|
|
call("senden dahinter", () -> {
|
|
send("20.0.0.2", "dahinter");
|
|
return "";
|
|
});
|
|
break;
|
|
case "routedPingBesideUdp":
|
|
call("sendProbe 20.0.0.2", () -> icmp(network.system(HOST).holeICMP().sendProbe("20.0.0.2", 64, 1, 1)));
|
|
call("senden verloren", () -> {
|
|
send("20.0.0.9", "verloren");
|
|
return "";
|
|
});
|
|
Thread.sleep(100);
|
|
call("sendProbe 20.0.0.2", () -> icmp(network.system(HOST).holeICMP().sendProbe("20.0.0.2", 64, 1, 2)));
|
|
break;
|
|
case "routedPingBehindPing":
|
|
call("sendeICMP 20.0.0.9", () -> {
|
|
network.system(HOST).holeICMP().sendeICMP(ICMP.TYPE_ECHO_REQUEST, ICMP.CODE_ECHO, 2, 2, null, "20.0.0.9");
|
|
return "";
|
|
});
|
|
call("sendProbe 20.0.0.2", () -> icmp(network.system(HOST).holeICMP().sendProbe("20.0.0.2", 64, 1, 1)));
|
|
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);
|
|
}
|
|
}
|