Files
mdg-filius/tools/java-oracle/src/NatRun.java
T
julian.kiedaischandClaude Opus 5.5 21a404693c feat(core): the packet capture's table after Java's Lauscher, measured against Java's own tables
captureRows.ts builds Java's rows per frame (Lauscher.datenVorbereiten):
Ethernet; ARP, ICMP or IP; TCP or UDP; the data with Java's protocol
recognition; with addresses and ports or ports only. The comment stays
structured; captureCommentText says it with given words and numbers.

ScenarioNetwork writes Java's finished tables per card (Lauscher.getDaten,
German locale); all runtime runs are recorded again, NatRun has ttlOut.
expectSameCaptures compares both views for every frame. Fixed where the
engine differed: an ARP request's target MAC is the broadcast address,
an ICMP error's identifier and sequence number are 0 (RFC 792), and host
unreachable from a router carries the packet as forwarded. Not taken:
Java's lower-case broadcast address in a reply to 0.0.0.0, and the typo
'Exeeded'. RIP keeps its recordings: Java's staggered router start now
gives a different startup on this machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:11:05 +00:00

235 lines
9.2 KiB
Java

// 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
// ttlOut 192.168.0.10 pings 42.0.0.20 with TTL 1: time exceeded
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 "ttlOut":
call("sendProbe 42.0.0.20", () -> icmp(os(LAN_PC).holeICMP().sendProbe("42.0.0.20", 1, 1, 1)));
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);
}
}