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>
231 lines
8.9 KiB
Java
231 lines
8.9 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
|
|
|
|
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);
|
|
}
|
|
}
|