// 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 // // 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 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 ". 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 ", 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 table = new TreeSet<>(); NatGateway nat = (NatGateway) ((GatewayFirmware) os(GATEWAY)).holeFirewall(); for (Map.Entry 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); } }