test(java-oracle): NatRun records the home router's NAT as Java FILIUS runs it
ScenarioNetwork builds home routers (their first card the WAN port) and their port forwarding (portForwarding, through GatewayFirmware.setStaticNAT). NatRun plays six stories on nat-home's network and writes the frames, the calls and the router's NAT table: a ping, a UDP exchange, the same socket to a second server (whose answer never reaches the LAN: Java keeps one entry per LAN endpoint, for the first remote only), a TCP connection out, a forwarded one in, and pings at the WAN address and past it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
// Runs nat-home's network in Java FILIUS (ScenarioNetwork) and records what
|
||||
// the home router's NAT really does: every frame each card saw, the story's
|
||||
// calls and, at the end, the router's NAT table.
|
||||
//
|
||||
// NatRun <scenario.json> <story> <milliseconds> <out.json>
|
||||
//
|
||||
// PC 192.168.0.10 on the home router's LAN port (192.168.0.1); its WAN port
|
||||
// (42.0.0.10/8) on a switch with 42.0.0.20 (gateway 42.0.0.10) and
|
||||
// 42.0.0.30. Port forwarding: TCP 8080 to 192.168.0.10:80. The stories:
|
||||
// pingOut 192.168.0.10 pings 42.0.0.20
|
||||
// udpOut 192.168.0.10 sends a datagram to 42.0.0.20:7, which answers
|
||||
// udpTwoRemotes the same, then from the same local port to 42.0.0.30:7,
|
||||
// which answers too
|
||||
// tcpOut 192.168.0.10 connects to 42.0.0.20:80, sends, receives, closes
|
||||
// forward 42.0.0.20 connects to 42.0.0.10:8080, forwarded to
|
||||
// 192.168.0.10:80; sends, receives, closes
|
||||
// pingWan 42.0.0.20 pings 42.0.0.10, then 192.168.0.10
|
||||
|
||||
import filius.software.nat.NatEntry;
|
||||
import filius.software.nat.NatGateway;
|
||||
import filius.software.nat.PortProtocolPair;
|
||||
import filius.software.system.GatewayFirmware;
|
||||
import filius.software.system.InternetKnotenBetriebssystem;
|
||||
import filius.software.transportschicht.ServerSocket;
|
||||
import filius.software.transportschicht.Socket;
|
||||
import filius.software.transportschicht.TCPSocket;
|
||||
import filius.software.transportschicht.UDPSocket;
|
||||
import filius.software.vermittlungsschicht.IcmpPaket;
|
||||
import filius.software.vermittlungsschicht.IpPaket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class NatRun {
|
||||
static final String LAN_PC = "00000000-0000-0000-0000-000000000501";
|
||||
static final String GATEWAY = "00000000-0000-0000-0000-000000000502";
|
||||
static final String SERVER = "00000000-0000-0000-0000-000000000504";
|
||||
static final String OTHER_SERVER = "00000000-0000-0000-0000-000000000505";
|
||||
|
||||
static ScenarioNetwork network;
|
||||
static final List<Object[]> events = new ArrayList<>();
|
||||
|
||||
static synchronized void event(String what) {
|
||||
events.add(new Object[] { System.currentTimeMillis() - network.start, what });
|
||||
}
|
||||
|
||||
interface Call {
|
||||
Object run() throws Exception;
|
||||
}
|
||||
|
||||
static Object call(String label, Call call) {
|
||||
event(label + " called");
|
||||
try {
|
||||
Object result = call.run();
|
||||
event(label + " returned" + (result == null ? " null" : " " + result));
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
event(label + " failed " + e.getClass().getSimpleName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void inThread(Call body) {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
body.run();
|
||||
} catch (Exception e) {
|
||||
event("thread failed " + e.getClass().getSimpleName());
|
||||
}
|
||||
});
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
static InternetKnotenBetriebssystem os(String node) {
|
||||
return network.system(node);
|
||||
}
|
||||
|
||||
static String icmp(IcmpPaket packet) {
|
||||
return packet == null ? "null" : "ICMP " + packet.getIcmpType() + "/" + packet.getIcmpCode() + " from " + packet.getSender();
|
||||
}
|
||||
|
||||
/**
|
||||
* A UDP server on the node's port 7: answers the first datagram with
|
||||
* "pong <it>". An active socket (a passive one registers its port only
|
||||
* after data came, which cannot come before); it answers to where the
|
||||
* datagram came from.
|
||||
*/
|
||||
static void udpEcho(String node, String label) {
|
||||
inThread(() -> {
|
||||
UDPSocket socket = new UDPSocket(os(node), "0.0.0.0", 1, 7);
|
||||
socket.verbinden();
|
||||
String message = socket.empfangen();
|
||||
event(label + " received " + message);
|
||||
socket.senden("pong " + message);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/** A TCP server on the node's port 80: receives one message, answers "reply <it>", and closes when the client does. */
|
||||
static void tcpServer(String node) {
|
||||
inThread(() -> {
|
||||
ServerSocket listener = new ServerSocket(os(node), 80, IpPaket.TCP, new ArrayList<>());
|
||||
Socket accepted = (Socket) call("server oeffnen", listener::oeffnen);
|
||||
String message = (String) call("server empfangen", accepted::empfangen);
|
||||
call("server senden", () -> {
|
||||
accepted.senden("reply " + message);
|
||||
return "";
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
static void tcpClient(String node, String target, int port) throws Exception {
|
||||
TCPSocket client = new TCPSocket(os(node), target, port);
|
||||
call("client verbinden", () -> {
|
||||
client.verbinden();
|
||||
return "";
|
||||
});
|
||||
call("client senden", () -> {
|
||||
client.senden("hello");
|
||||
return "";
|
||||
});
|
||||
call("client empfangen", client::empfangen);
|
||||
call("client schliessen", () -> {
|
||||
client.schliessen();
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
static long rtt() {
|
||||
return filius.hardware.Verbindung.holeRTT();
|
||||
}
|
||||
|
||||
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]);
|
||||
network.start();
|
||||
Thread.sleep(500);
|
||||
|
||||
switch (story) {
|
||||
case "pingOut":
|
||||
call("sendProbe 42.0.0.20", () -> icmp(os(LAN_PC).holeICMP().sendProbe("42.0.0.20", 64, 1, 1)));
|
||||
break;
|
||||
case "udpOut": {
|
||||
udpEcho(SERVER, "server");
|
||||
Thread.sleep(200);
|
||||
UDPSocket socket = new UDPSocket(os(LAN_PC), "42.0.0.20", 7);
|
||||
socket.verbinden();
|
||||
call("senden 42.0.0.20", () -> {
|
||||
socket.senden("one");
|
||||
return "";
|
||||
});
|
||||
call("empfangen", () -> socket.empfangen(5 * rtt()));
|
||||
break;
|
||||
}
|
||||
case "udpTwoRemotes": {
|
||||
udpEcho(SERVER, "server");
|
||||
udpEcho(OTHER_SERVER, "other server");
|
||||
Thread.sleep(200);
|
||||
UDPSocket first = new UDPSocket(os(LAN_PC), "42.0.0.20", 7);
|
||||
first.verbinden();
|
||||
call("senden 42.0.0.20", () -> {
|
||||
first.senden("one");
|
||||
return "";
|
||||
});
|
||||
call("empfangen", () -> first.empfangen(5 * rtt()));
|
||||
int port = first.holeLokalenPort();
|
||||
first.schliessen();
|
||||
UDPSocket second = new UDPSocket(os(LAN_PC), "42.0.0.30", 7, port);
|
||||
second.verbinden();
|
||||
call("senden 42.0.0.30", () -> {
|
||||
second.senden("two");
|
||||
return "";
|
||||
});
|
||||
call("empfangen", () -> second.empfangen(5 * rtt()));
|
||||
break;
|
||||
}
|
||||
case "tcpOut":
|
||||
tcpServer(SERVER);
|
||||
Thread.sleep(200);
|
||||
tcpClient(LAN_PC, "42.0.0.20", 80);
|
||||
break;
|
||||
case "forward":
|
||||
tcpServer(LAN_PC);
|
||||
Thread.sleep(200);
|
||||
tcpClient(SERVER, "42.0.0.10", 8080);
|
||||
break;
|
||||
case "pingWan":
|
||||
call("sendProbe 42.0.0.10", () -> icmp(os(SERVER).holeICMP().sendProbe("42.0.0.10", 64, 1, 1)));
|
||||
call("sendProbe 192.168.0.10", () -> icmp(os(SERVER).holeICMP().sendProbe("192.168.0.10", 64, 1, 2)));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown story " + story);
|
||||
}
|
||||
sleepUntil(end);
|
||||
|
||||
// The NAT table: `protocol address:port -> lan address:port type`, sorted.
|
||||
TreeSet<String> table = new TreeSet<>();
|
||||
NatGateway nat = (NatGateway) ((GatewayFirmware) os(GATEWAY)).holeFirewall();
|
||||
for (Map.Entry<PortProtocolPair, NatEntry> entry : nat.getNATTable().getDynamicNATTable().entrySet()) {
|
||||
table.add(entry.getKey().getProtocol() + " " + entry.getKey().getAddress() + ":" + entry.getKey().getPort() + " -> "
|
||||
+ entry.getValue().getInetAddress() + " " + entry.getValue().getNatType());
|
||||
}
|
||||
|
||||
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
|
||||
out.open('{');
|
||||
network.writeFrames(out);
|
||||
synchronized (NatRun.class) {
|
||||
out.key("events").open('[');
|
||||
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).close(']');
|
||||
out.close(']');
|
||||
}
|
||||
out.key("nat").open('[');
|
||||
for (String each : table) out.string(each);
|
||||
out.close(']');
|
||||
out.close('}');
|
||||
network.stop();
|
||||
ScenarioNetwork.write(args[3], out);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user