diff --git a/config/nodes/node1.properties b/config/nodes/node1.properties deleted file mode 100644 index 6ec89e4..0000000 --- a/config/nodes/node1.properties +++ /dev/null @@ -1,3 +0,0 @@ -key=AAAAAAAAAAA= -name="Node Number One" -host=example.com \ No newline at end of file diff --git a/pom.xml b/pom.xml index fef70cb..ff03829 100644 --- a/pom.xml +++ b/pom.xml @@ -15,11 +15,6 @@ - - org.java-websocket - Java-WebSocket - 1.5.7 - org.json json diff --git a/src/main/java/eu/m724/autopeerer/client/ClientPrefs.java b/src/main/java/eu/m724/autopeerer/client/ClientPrefs.java deleted file mode 100644 index 3072aef..0000000 --- a/src/main/java/eu/m724/autopeerer/client/ClientPrefs.java +++ /dev/null @@ -1,45 +0,0 @@ -package eu.m724.autopeerer.client; - -public class ClientPrefs { - private static ClientPrefs INSTANCE; - - private final boolean ipv4, restricted4, ipv6, restricted6; - - private ClientPrefs( boolean ipv4, boolean restricted4, boolean ipv6, boolean restricted6) { - this.ipv4 = ipv4; - this.restricted4 = restricted4; - this.ipv6 = ipv6; - this.restricted6 = restricted6; - } - - public static void init(ClientConfiguration configuration) { - var ipv4 = configuration.getString("stack.ipv4"); - var ipv6 = configuration.getString("stack.ipv6"); - - INSTANCE = new ClientPrefs( - ipv4.equalsIgnoreCase("yes") || ipv4.equalsIgnoreCase("restricted"), - ipv4.equalsIgnoreCase("restricted"), - ipv6.equalsIgnoreCase("yes") || ipv6.equalsIgnoreCase("restricted"), - ipv6.equalsIgnoreCase("restricted") - ); - } - - /* */ - - public static boolean ipv4Supported() { - return INSTANCE.ipv4; - } - - public static boolean ipv6Supported() { - return INSTANCE.ipv6; - } - - public static boolean ipv4Restricted() { - return INSTANCE.restricted4; - } - - public static boolean ipv6Restricted() { - return INSTANCE.restricted6; - } - -} diff --git a/src/main/java/eu/m724/autopeerer/client/Main.java b/src/main/java/eu/m724/autopeerer/client/Main.java index ab2cfa9..c9250ce 100644 --- a/src/main/java/eu/m724/autopeerer/client/Main.java +++ b/src/main/java/eu/m724/autopeerer/client/Main.java @@ -1,16 +1,18 @@ package eu.m724.autopeerer.client; import eu.m724.autopeerer.client.bird.BirdLive; +import eu.m724.autopeerer.client.connectivity.Connectivity; import eu.m724.autopeerer.client.wireguard.WireGuardLive; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.URI; +import java.util.ArrayList; +import java.util.Set; public class Main { - public static void main(String[] args) throws InterruptedException { - System.out.println("Hello world!"); - + public static void main(String[] args) { var config = new ClientConfiguration(); try { config.load(); @@ -23,16 +25,34 @@ public class Main { return; } - ClientPrefs.init(config); + System.out.print("Checking connectivity"); + Connectivity.check(); - URI serverUri = URI.create(config.getString("remote")); + var list = new ArrayList(); + + if (Connectivity.ipv6()) list.add("IPv6"); + if (Connectivity.ipv4()) list.add("IPv4"); + + if (!list.isEmpty()) { + System.out.println(": " + String.join(" + ", list)); + } else { + System.err.println(": You have no internet"); + } + + var remote = config.getString("remote").split(":"); + var serverAddress = new InetSocketAddress(remote[0], Integer.parseInt(remote[1])); var wireGuardLive = new WireGuardLive(new File(config.getString("wireguard.directory"))); var birdLive = new BirdLive(new File(config.getString("bird.directory"))); - var packetHandler = new PacketHandler(wireGuardLive, birdLive, config.getString("link-local")); - var client = new MyWebsocketClient(serverUri, packetHandler); + var packetHandler = new PacketHandler(wireGuardLive, birdLive); + var client = new MyTcpClient(serverAddress, config.getString("key"), packetHandler); - client.connect(); + try { + client.connect(); + } catch (IOException e) { + System.err.println("Error connecting"); + throw new RuntimeException(e); + } } } \ No newline at end of file diff --git a/src/main/java/eu/m724/autopeerer/client/MyTcpClient.java b/src/main/java/eu/m724/autopeerer/client/MyTcpClient.java new file mode 100644 index 0000000..d3ddcd2 --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/client/MyTcpClient.java @@ -0,0 +1,64 @@ +package eu.m724.autopeerer.client; + +import eu.m724.autopeerer.common.packet.PacketReaderImpl; +import eu.m724.autopeerer.common.packet.PacketReaderRunnable; +import eu.m724.autopeerer.common.packet.Packets; +import eu.m724.autopeerer.common.packet.c2s.LoginPacket; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.concurrent.Executors; + +public class MyTcpClient { + private final InetSocketAddress serverAddress; + private final byte[] key; + private final PacketHandler packetHandler; + + private Socket socket; + + public MyTcpClient(InetSocketAddress serverAddress, String key, PacketHandler packetHandler) { + this.serverAddress = serverAddress; + this.key = Base64.getDecoder().decode(key); + this.packetHandler = packetHandler; + } + + public void connect() throws IOException { + var connectStart = System.nanoTime(); + + this.socket = new Socket(serverAddress.getAddress(), serverAddress.getPort()); + + double connectTime = (System.nanoTime() - connectStart) / 1000000.0; + System.out.printf("Connected in %.3f ms\n", connectTime); + + packetHandler.outputStream = socket.getOutputStream(); + Packets.send(new LoginPacket(key), socket.getOutputStream()); + + Executors.newSingleThreadExecutor().execute(new PacketReaderRunnable(socket, new PacketReaderImpl() { + @Override + public void onPacket(ByteBuffer byteBuffer) { + try { + packetHandler.handle(byteBuffer); + } catch (IOException e) { + System.err.println("Error handling packet"); + throw new RuntimeException(e); + } + } + + @Override + public void onError(Exception exception) { + System.err.println("Connection error:"); + exception.printStackTrace(); + } + + @Override + public void onClose() { + System.out.println("Connection closed"); + } + })); + + while (socket.isConnected()) { } // TODO + } +} diff --git a/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java b/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java deleted file mode 100644 index 316312d..0000000 --- a/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java +++ /dev/null @@ -1,58 +0,0 @@ -package eu.m724.autopeerer.client; - -import eu.m724.autopeerer.common.packet.Packets; -import eu.m724.autopeerer.common.packet.c2s.LoginPacket; -import org.java_websocket.client.WebSocketClient; -import org.java_websocket.handshake.ServerHandshake; - -import java.net.URI; -import java.nio.ByteBuffer; - -public class MyWebsocketClient extends WebSocketClient { - private final PacketHandler packetHandler; - private long connectStart; - - public MyWebsocketClient(URI serverUri, PacketHandler packetHandler) { - super(serverUri); - this.packetHandler = packetHandler; - packetHandler.sender = this::send; - } - - @Override - public void connect() { - super.connect(); - connectStart = System.nanoTime(); - } - - @Override - public void onOpen(ServerHandshake serverHandshake) { - double connectTime = (System.nanoTime() - connectStart) / 1000000.0; - System.out.printf("Connected in %.3f ms\n", connectTime); - - Packets.send(new LoginPacket(new byte[8]), this::send); - } - - @Override - public void onMessage(ByteBuffer bytes) { - packetHandler.handle(bytes); - } - - @Override - public void onClose(int code, String reason, boolean remote) { - if (remote) { - System.out.println("Closed by remote: " + code + " " + reason); - } else { - System.out.println("Closed by client: " + code + " " + reason); - } - } - - @Override - public void onError(Exception e) { - System.err.println("WS error: "); - e.printStackTrace(); - } - - // we don't do String - @Override - public void onMessage(String s) { } -} diff --git a/src/main/java/eu/m724/autopeerer/client/PacketHandler.java b/src/main/java/eu/m724/autopeerer/client/PacketHandler.java index 28632f9..ed95c98 100644 --- a/src/main/java/eu/m724/autopeerer/client/PacketHandler.java +++ b/src/main/java/eu/m724/autopeerer/client/PacketHandler.java @@ -2,12 +2,14 @@ package eu.m724.autopeerer.client; import eu.m724.autopeerer.client.bird.BirdLive; import eu.m724.autopeerer.client.bird.BirdSession; +import eu.m724.autopeerer.client.connectivity.Connectivity; import eu.m724.autopeerer.client.wireguard.WireGuardKeys; import eu.m724.autopeerer.client.wireguard.WireGuardLive; import eu.m724.autopeerer.client.wireguard.WireGuardSession; import eu.m724.autopeerer.common.AddressTools; import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packets; +import eu.m724.autopeerer.common.packet.s2c.LoginResponsePacket; import eu.m724.autopeerer.common.packet.s2c.PingRequestPacket; import eu.m724.autopeerer.common.packet.s2c.SessionRequestPacket; import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket; @@ -15,6 +17,8 @@ import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket; import java.io.BufferedReader; import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; @@ -22,22 +26,22 @@ import java.nio.file.FileAlreadyExistsException; import java.util.Base64; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Consumer; public class PacketHandler { - Consumer sender; + OutputStream outputStream; + private boolean loggedIn; + + private InetAddress linkLocal; private final WireGuardLive wireGuardLive; private final BirdLive birdLive; - private final String serverLinkLocal; - public PacketHandler(WireGuardLive wireGuardLive, BirdLive birdLive, String serverLinkLocal) { + public PacketHandler(WireGuardLive wireGuardLive, BirdLive birdLive) { this.wireGuardLive = wireGuardLive; this.birdLive = birdLive; - this.serverLinkLocal = serverLinkLocal; } - void handle(ByteBuffer bytes) { + void handle(ByteBuffer bytes) throws IOException { Packet p; try { p = Packets.parseClient(bytes); @@ -55,10 +59,18 @@ public class PacketHandler { throw new RuntimeException(e); } - if (p instanceof PingRequestPacket packet) { - handlePingRequest(packet); - } else if (p instanceof SessionRequestPacket packet) { - handleSessionRequest(packet); + if (loggedIn) { + if (p instanceof PingRequestPacket packet) { + handlePingRequest(packet); + } else if (p instanceof SessionRequestPacket packet) { + handleSessionRequest(packet); + } + } else { + if (p instanceof LoginResponsePacket packet) { + this.linkLocal = packet.linkLocal; + System.out.println("Logged in as " + packet.nodeId); + loggedIn = true; + } } } @@ -87,32 +99,33 @@ public class PacketHandler { } } } + + System.out.printf("Ping request #%d to %s - %s avg %.3f / mdev %.3f ms\n", packet.requestId, packet.target.getHostAddress(), status, average, meanDeviation); + Packets.send(new PingResponsePacket(packet.requestId, status, average, meanDeviation), outputStream); } catch (IOException e) { - System.err.println("Error pinging"); + System.err.println("Error executing ping request"); e.printStackTrace(); } - System.out.printf("Ping request #%d to %s - %s avg %.3f / mdev %.3f ms\n", packet.requestId, packet.target.getHostAddress(), status, average, meanDeviation); - Packets.send(new PingResponsePacket(packet.requestId, status, average, meanDeviation), sender); }); } - private void handleSessionRequest(SessionRequestPacket packet) { + private void handleSessionRequest(SessionRequestPacket packet) throws IOException { // validate endpoint var resolved = false; try { - if (ClientPrefs.ipv6Supported()) { + if (Connectivity.ipv6()) { var res = AddressTools.resolve(packet.endpointHost, true); resolved = res != null; } - if (!resolved && ClientPrefs.ipv4Supported()) { + if (!resolved && Connectivity.ipv4()) { var res = AddressTools.resolve(packet.endpointHost, true); resolved = res != null; } } catch (UnknownHostException | AddressTools.MultipleRecordsException ignored) { } if (!resolved) { - Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR_RESOLVE, -1, null), sender); + Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR_RESOLVE, -1, null), outputStream); return; } @@ -124,22 +137,43 @@ public class PacketHandler { port = (int) (packet.asn % 10000); } - var wireGuardSession = new WireGuardSession(port, privateKey, serverLinkLocal, packet.linkLocal.getHostAddress(), packet.endpointHost + ":" + packet.endpointPort, packet.publicKey); - var birdSession = new BirdSession(packet.asn, packet.linkLocal.getHostAddress()); + var wireGuardSession = new WireGuardSession( + port, + privateKey, + linkLocal.getHostAddress(), + packet.linkLocal.getHostAddress(), + packet.endpointHost + ":" + packet.endpointPort, + packet.publicKey + ); + + var birdSession = new BirdSession( + packet.asn, + packet.linkLocal.getHostAddress() + ); try { wireGuardLive.saveSession(packet.asn, wireGuardSession); birdLive.saveSession(birdSession); System.out.printf("Created session AS%d to %s\n", packet.asn, packet.endpointHost); - Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.OK, wireGuardSession.listenPort(), publicKey), sender); + Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.OK, wireGuardSession.listenPort(), publicKey), outputStream); } catch (FileAlreadyExistsException e) { System.err.println("Tried to create a session which already exists: AS" + packet.asn); - Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR_DUPLICATE, -1, null), sender); + Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR_DUPLICATE, -1, null), outputStream); } catch (IOException e) { - Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR_OTHER, -1, null), sender); - System.err.println("Failed to save session"); - throw new RuntimeException(e); + Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR_OTHER, -1, null), outputStream); + + var ex = new RuntimeException("Failed to save session", e); + + // clean up + try { + wireGuardLive.removeSession(packet.asn); + birdLive.removeSession(packet.asn); + } catch (Exception e1) { + ex = new RuntimeException("Failed to clean up after failing to save session", ex); + } + + throw ex; } } } diff --git a/src/main/java/eu/m724/autopeerer/client/bird/BirdLive.java b/src/main/java/eu/m724/autopeerer/client/bird/BirdLive.java index 391ceba..367062b 100644 --- a/src/main/java/eu/m724/autopeerer/client/bird/BirdLive.java +++ b/src/main/java/eu/m724/autopeerer/client/bird/BirdLive.java @@ -18,6 +18,11 @@ public class BirdLive { Files.writeString(file.toPath(), session.config(), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); } + public void removeSession(long asn) throws IOException { + File file = new File(configsPath, "ap_" + asn + ".conf"); + Files.delete(file.toPath()); + } + public BirdSession getSession(long asn) throws IOException { File file = new File(configsPath, "ap_" + asn + ".conf"); diff --git a/src/main/java/eu/m724/autopeerer/client/connectivity/Connectivity.java b/src/main/java/eu/m724/autopeerer/client/connectivity/Connectivity.java new file mode 100644 index 0000000..20a6cff --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/client/connectivity/Connectivity.java @@ -0,0 +1,39 @@ +package eu.m724.autopeerer.client.connectivity; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +public class Connectivity { + private static boolean ipv4, ipv6; + + public static boolean ipv4() { + return ipv4; + } + + public static boolean ipv6() { + return ipv6; + } + + public static void check() { + var client = HttpClient.newHttpClient(); + + var future4 = composeRequest(client, false); + var future6 = composeRequest(client, true); + + ipv4 = future4.join(); + ipv6 = future6.join(); + } + + private static CompletableFuture composeRequest(HttpClient client, boolean ipv6) { + var request = HttpRequest + .newBuilder(URI.create("https://ipv%d.google.com/generate_204".formatted(ipv6 ? 6 : 4))) + .timeout(Duration.ofSeconds(5)) + .build(); + + return client.sendAsync(request, HttpResponse.BodyHandlers.discarding()).handle((r, ex) -> ex == null); + } +} diff --git a/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardLive.java b/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardLive.java index 137ccf7..d425ea3 100644 --- a/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardLive.java +++ b/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardLive.java @@ -17,6 +17,11 @@ public class WireGuardLive { Files.writeString(file.toPath(), session.config(), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); } + public void removeSession(long asn) throws IOException { + File file = new File(configsPath, "ap_" + asn + ".conf"); + Files.delete(file.toPath()); + } + public WireGuardSession getSession(long asn) throws IOException { File file = new File(configsPath, "ap_" + asn + ".conf"); diff --git a/src/main/java/eu/m724/autopeerer/common/AddressTools.java b/src/main/java/eu/m724/autopeerer/common/AddressTools.java index e86a89e..21470c8 100644 --- a/src/main/java/eu/m724/autopeerer/common/AddressTools.java +++ b/src/main/java/eu/m724/autopeerer/common/AddressTools.java @@ -17,10 +17,10 @@ public class AddressTools { * @return whether it's IPv6 and link local */ public static boolean isLinkLocal(InetAddress address) { - if (isIp(address, true)) return false; + if (!isIp(address, true)) return false; var bytes = address.getAddress(); - return bytes[0] == -2 && bytes[1] >= 0 && bytes[1] <= 63; + return bytes[0] == -2 && bytes[1] <= -65; } /** diff --git a/src/main/java/eu/m724/autopeerer/common/NodeProfile.java b/src/main/java/eu/m724/autopeerer/common/NodeProfile.java new file mode 100644 index 0000000..84be3c2 --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/common/NodeProfile.java @@ -0,0 +1,14 @@ +package eu.m724.autopeerer.common; + +import java.net.Inet6Address; + +public record NodeProfile( + String label, + String hostname, + Inet6Address linkLocal, + boolean supportsIpv4, + boolean supportsIpv6, + boolean restrictedIpv4, + boolean restrictedIpv6 +) { +} diff --git a/src/main/java/eu/m724/autopeerer/common/packet/PacketReaderImpl.java b/src/main/java/eu/m724/autopeerer/common/packet/PacketReaderImpl.java new file mode 100644 index 0000000..1a86987 --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/common/packet/PacketReaderImpl.java @@ -0,0 +1,9 @@ +package eu.m724.autopeerer.common.packet; + +import java.nio.ByteBuffer; + +public interface PacketReaderImpl { + void onPacket(ByteBuffer byteBuffer); + void onError(Exception exception); + void onClose(); +} diff --git a/src/main/java/eu/m724/autopeerer/common/packet/PacketReaderRunnable.java b/src/main/java/eu/m724/autopeerer/common/packet/PacketReaderRunnable.java new file mode 100644 index 0000000..e43cff5 --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/common/packet/PacketReaderRunnable.java @@ -0,0 +1,38 @@ +package eu.m724.autopeerer.common.packet; + +import java.io.IOException; +import java.net.Socket; +import java.nio.ByteBuffer; + +public class PacketReaderRunnable implements Runnable { + private final Socket socket; + private final PacketReaderImpl impl; + + public PacketReaderRunnable(Socket socket, PacketReaderImpl impl) { + this.socket = socket; + this.impl = impl; + } + + @Override + public void run() { + try { + while (socket.isConnected()) { + loop(); + } + } catch (Exception e) { + impl.onError(e); + } + + impl.onClose(); + } + + private void loop() throws IOException { + var is = socket.getInputStream(); + + var bs = is.readNBytes(2); + var length = bs[0] << 8 | bs[1]; + + var buffer = ByteBuffer.wrap(is.readNBytes(length)); // packets are small + impl.onPacket(buffer); + } +} diff --git a/src/main/java/eu/m724/autopeerer/common/packet/Packets.java b/src/main/java/eu/m724/autopeerer/common/packet/Packets.java index 01b0528..b1142e3 100644 --- a/src/main/java/eu/m724/autopeerer/common/packet/Packets.java +++ b/src/main/java/eu/m724/autopeerer/common/packet/Packets.java @@ -7,12 +7,14 @@ import eu.m724.autopeerer.common.packet.s2c.LoginResponsePacket; import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket; import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket; +import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.function.Consumer; public class Packets { public static Packet parseClient(ByteBuffer buffer) throws Exception { - byte id = buffer.get(); + var id = buffer.get(); Packet packet = null; @@ -28,7 +30,7 @@ public class Packets { } public static Packet parseServer(ByteBuffer buffer) throws Exception { - byte id = buffer.get(); + var id = buffer.get(); Packet packet = null; @@ -47,7 +49,10 @@ public class Packets { ByteBuffer packetBuffer = packet.serialize(); packetBuffer.rewind(); - var bb = ByteBuffer.allocate(1 + packetBuffer.remaining()); + var packetLength = 1 + packetBuffer.remaining(); + + var bb = ByteBuffer.allocate(2 + packetLength); + bb.putShort((short) packetLength); bb.put(packet.getId()); bb.put(packetBuffer); bb.rewind(); @@ -59,4 +64,8 @@ public class Packets { sender.accept(compose(packet)); } + public static void send(Packet packet, OutputStream outputStream) throws IOException { + outputStream.write(compose(packet).array()); + } + } diff --git a/src/main/java/eu/m724/autopeerer/common/packet/c2s/LoginPacket.java b/src/main/java/eu/m724/autopeerer/common/packet/c2s/LoginPacket.java index 30075ee..4b21fa7 100644 --- a/src/main/java/eu/m724/autopeerer/common/packet/c2s/LoginPacket.java +++ b/src/main/java/eu/m724/autopeerer/common/packet/c2s/LoginPacket.java @@ -1,7 +1,11 @@ package eu.m724.autopeerer.common.packet.c2s; +import eu.m724.autopeerer.common.NodeProfile; import eu.m724.autopeerer.common.packet.Packet; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; public class LoginPacket implements Packet { @@ -21,6 +25,7 @@ public class LoginPacket implements Packet { public ByteBuffer serialize() { var buffer = ByteBuffer.allocate(8); buffer.put(key); + return buffer; } diff --git a/src/main/java/eu/m724/autopeerer/common/packet/s2c/LoginResponsePacket.java b/src/main/java/eu/m724/autopeerer/common/packet/s2c/LoginResponsePacket.java index c524edb..28b6572 100644 --- a/src/main/java/eu/m724/autopeerer/common/packet/s2c/LoginResponsePacket.java +++ b/src/main/java/eu/m724/autopeerer/common/packet/s2c/LoginResponsePacket.java @@ -1,15 +1,20 @@ package eu.m724.autopeerer.common.packet.s2c; +import eu.m724.autopeerer.common.AddressTools; import eu.m724.autopeerer.common.packet.Packet; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; public class LoginResponsePacket implements Packet { public final String nodeId; + public final InetAddress linkLocal; - public LoginResponsePacket(String nodeId) { + public LoginResponsePacket(String nodeId, InetAddress linkLocal) { this.nodeId = nodeId; + this.linkLocal = linkLocal; } @Override @@ -20,9 +25,13 @@ public class LoginResponsePacket implements Packet { @Override public ByteBuffer serialize() { var nodeIdEncoded = nodeId.getBytes(StandardCharsets.UTF_8); - var buffer = ByteBuffer.allocate(1 + nodeIdEncoded.length); + var buffer = ByteBuffer.allocate(1 + nodeIdEncoded.length + 16); + buffer.put((byte) nodeIdEncoded.length); buffer.put(nodeIdEncoded); + + buffer.put(linkLocal.getAddress()); + return buffer; } @@ -31,6 +40,20 @@ public class LoginResponsePacket implements Packet { var nodeIdEncoded = new byte[size]; buffer.get(nodeIdEncoded); - return new LoginResponsePacket(new String(nodeIdEncoded, StandardCharsets.UTF_8)); + var ll = new byte[16]; + buffer.get(ll); + InetAddress linkLocal; + try { + linkLocal = InetAddress.getByAddress(ll); + } catch (UnknownHostException e) { + linkLocal = null; + } + + assert AddressTools.isLinkLocal(linkLocal); + + return new LoginResponsePacket( + new String(nodeIdEncoded, StandardCharsets.UTF_8), + linkLocal + ); } } diff --git a/src/main/java/eu/m724/autopeerer/server/ClientState.java b/src/main/java/eu/m724/autopeerer/server/ClientState.java index 3b0647e..875522a 100644 --- a/src/main/java/eu/m724/autopeerer/server/ClientState.java +++ b/src/main/java/eu/m724/autopeerer/server/ClientState.java @@ -4,67 +4,37 @@ import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packets; import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket; import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket; -import eu.m724.autopeerer.common.packet.s2c.PingRequestPacket; -import eu.m724.autopeerer.common.packet.s2c.SessionRequestPacket; -import org.java_websocket.WebSocket; -import java.net.InetAddress; -import java.nio.ByteBuffer; +import java.io.IOException; +import java.net.Socket; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ThreadLocalRandom; import java.util.function.Consumer; public class ClientState { public boolean authenticated = false; public final int clientId; - public final WebSocket socket; + public final Socket socket; Node node; - private final Consumer sender; - - public ClientState(int clientId, WebSocket socket) { + public ClientState(int clientId, Socket socket) { this.clientId = clientId; this.socket = socket; - this.sender = socket::send; } - void send(Packet packet) { - Packets.send(packet, sender); + void send(Packet packet) throws IOException { + Packets.send(packet, socket.getOutputStream()); } - void end() { + void end() throws IOException { socket.close(); } - - CompletableFuture ping(InetAddress host) { - var future = new CompletableFuture(); - - var id = (short) (ThreadLocalRandom.current().nextInt() & 0xFFFF); - - pingConsumers.put(id, future::complete); - send(new PingRequestPacket(id, host)); - - return future; - } - - CompletableFuture session(long asn, InetAddress linkLocal, String publicKey, String endpointHost, int endpointPort) { - var future = new CompletableFuture(); - - sessionConsumers.put(asn, future::complete); - send(new SessionRequestPacket( - asn, linkLocal, publicKey, endpointHost, endpointPort - )); - - return future; - } /* Packet functions */ - private final Map> pingConsumers = new HashMap<>(); - private final Map> sessionConsumers = new HashMap<>(); + final Map> pingConsumers = new HashMap<>(); + final Map> sessionConsumers = new HashMap<>(); void onPacketReceived(Packet p) { if (p instanceof PingResponsePacket packet) { diff --git a/src/main/java/eu/m724/autopeerer/server/Main.java b/src/main/java/eu/m724/autopeerer/server/Main.java index dae011b..99c2070 100644 --- a/src/main/java/eu/m724/autopeerer/server/Main.java +++ b/src/main/java/eu/m724/autopeerer/server/Main.java @@ -31,7 +31,7 @@ public class Main { var packetHandler = new PacketHandler(nodes); - var server = new MyWebsocketServer( + var server = new MyTcpServer( new InetSocketAddress(config.getString("socket.address"), config.getInt("socket.port")), packetHandler ); diff --git a/src/main/java/eu/m724/autopeerer/server/MyHttpHandler.java b/src/main/java/eu/m724/autopeerer/server/MyHttpHandler.java index bda1e91..6926012 100644 --- a/src/main/java/eu/m724/autopeerer/server/MyHttpHandler.java +++ b/src/main/java/eu/m724/autopeerer/server/MyHttpHandler.java @@ -53,10 +53,14 @@ public class MyHttpHandler implements HttpHandler { var json = new JSONObject(); nodes.forEach(node -> { + var prof = node.getNodeProfile(); var val = new JSONObject() - .put("name", node.name()) - .put("host", node.host()) - .put("online", node.connected()); + .put("label", node.getNodeProfile().label()) + .put("host", node.getNodeProfile().hostname()) + .put("linkLocal", node.getNodeProfile().linkLocal().getHostAddress()) + .put("ipv4", prof.supportsIpv4() ? (prof.restrictedIpv4() ? "restricted" : true) : false) + .put("ipv6", prof.supportsIpv6() ? (prof.restrictedIpv6() ? "restricted" : true) : false) + .put("online", node.isConnected()); json.put(node.id(), val); }); @@ -88,7 +92,7 @@ public class MyHttpHandler implements HttpHandler { var futures = new HashSet>(); for (Node node : selectedNodes) { - if (!node.connected()) { // node is offline + if (!node.isConnected()) { // node is offline var response = new JSONObject() .put("node", node.id()) .put("status", "OFFLINE"); @@ -96,7 +100,7 @@ public class MyHttpHandler implements HttpHandler { continue; } - var future = node.getClient().ping(target).handle((result, ex) -> { + var future = node.ping(target).handle((result, ex) -> { try { if (ex != null) { sendResponse(exchange, 500); @@ -160,7 +164,7 @@ public class MyHttpHandler implements HttpHandler { CompletableFuture future = null; try { - future = node.getClient().session( + future = node.session( json.getLong("asn"), InetAddress.getByName(json.getString("linkLocal")), json.getString("publicKey"), diff --git a/src/main/java/eu/m724/autopeerer/server/MyTcpServer.java b/src/main/java/eu/m724/autopeerer/server/MyTcpServer.java new file mode 100644 index 0000000..5e8e0c8 --- /dev/null +++ b/src/main/java/eu/m724/autopeerer/server/MyTcpServer.java @@ -0,0 +1,84 @@ +package eu.m724.autopeerer.server; + +import eu.m724.autopeerer.common.packet.PacketReaderImpl; +import eu.m724.autopeerer.common.packet.PacketReaderRunnable; + +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; + +public class MyTcpServer { + private final InetSocketAddress address; + private final PacketHandler handler; + + private ServerSocket socket; + private int id = 0; + + private final Map states = new HashMap<>(); + + public MyTcpServer(InetSocketAddress address, PacketHandler packetHandler) throws IOException { + this.address = address; + this.handler = packetHandler; + } + + public void start() throws IOException { + this.socket = new ServerSocket(address.getPort(), 50, address.getAddress()); + System.out.printf("TCP server started on %s:%d\n", address.getAddress().getHostAddress(), address.getPort()); + + Executors.newSingleThreadExecutor().execute(() -> { + try { + loop(); + } catch (IOException e) { + System.err.println("Error in TCP loop"); + throw new RuntimeException(e); + } + }); + } + + private void loop() throws IOException { + while (!socket.isClosed()) { + Socket client = socket.accept(); + var id = ++this.id; + + var state = new ClientState(id, client); + states.put(id, state); + System.out.printf("[%d] Connected: %s\n", id, client.getRemoteSocketAddress()); + + Executors.newSingleThreadExecutor().execute( + new PacketReaderRunnable(client, new PacketReaderImpl() { + @Override + public void onPacket(ByteBuffer byteBuffer) { + try { + handler.handle(state, byteBuffer); + } catch (IOException e) { + System.err.println("Error handling packet"); + e.printStackTrace(); + } + } + + @Override + public void onError(Exception exception) { + // TODO + System.err.printf("[%d] Error\n", state.clientId); + exception.printStackTrace(); + } + + @Override + public void onClose() { + var state = states.remove(id); + state.node.setClient(null); + var id = state.authenticated ? state.node.id() : client.getRemoteSocketAddress(); + System.out.printf("[%d] Disconnected: %s\n", state.clientId, id); + } + }) + ); + } + } + + public void close() throws IOException { + this.socket.close(); + } +} diff --git a/src/main/java/eu/m724/autopeerer/server/MyWebsocketServer.java b/src/main/java/eu/m724/autopeerer/server/MyWebsocketServer.java deleted file mode 100644 index 14a8a8f..0000000 --- a/src/main/java/eu/m724/autopeerer/server/MyWebsocketServer.java +++ /dev/null @@ -1,59 +0,0 @@ -package eu.m724.autopeerer.server; - -import org.java_websocket.WebSocket; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.server.WebSocketServer; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; - -public class MyWebsocketServer extends WebSocketServer { - private final PacketHandler packetHandler; - - private final Map states = new HashMap<>(); - - private int id = 0; - - public MyWebsocketServer(InetSocketAddress address, PacketHandler packetHandler) { - super(address); - this.setReuseAddr(true); - - this.packetHandler = packetHandler; - } - - @Override - public void onOpen(WebSocket conn, ClientHandshake handshake) { - var state = new ClientState(++id, conn); - states.put(conn, state); - System.out.printf("[%d] Connected: %s\n", id, conn.getRemoteSocketAddress().getHostString()); - } - - @Override - public void onClose(WebSocket conn, int code, String reason, boolean remote) { - var state = states.remove(conn); - state.node.setClient(null); - var id = state.authenticated ? state.node.id() : conn.getRemoteSocketAddress().getHostString(); - System.out.printf("[%d] Disconnected: %s\n", state.clientId, id); - } - - @Override - public void onMessage(WebSocket conn, ByteBuffer message) { - packetHandler.handle(states.get(conn), message); - } - - @Override - public void onError(WebSocket conn, Exception ex) { - ex.printStackTrace(); - } - - @Override - public void onStart() { - System.out.printf("Websocket server started on %s:%d\n", getAddress().getHostString(), getPort()); - } - - @Override - public void onMessage(WebSocket conn, String message) { - } -} diff --git a/src/main/java/eu/m724/autopeerer/server/Node.java b/src/main/java/eu/m724/autopeerer/server/Node.java index fc0a3da..1adb40b 100644 --- a/src/main/java/eu/m724/autopeerer/server/Node.java +++ b/src/main/java/eu/m724/autopeerer/server/Node.java @@ -1,54 +1,80 @@ package eu.m724.autopeerer.server; +import eu.m724.autopeerer.common.AddressTools; +import eu.m724.autopeerer.common.NodeProfile; import eu.m724.autopeerer.common.packet.Packet; -import eu.m724.autopeerer.common.packet.Packets; +import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket; +import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket; +import eu.m724.autopeerer.common.packet.s2c.PingRequestPacket; +import eu.m724.autopeerer.common.packet.s2c.SessionRequestPacket; +import java.io.IOException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Base64; import java.util.Objects; import java.util.Properties; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadLocalRandom; public final class Node { private final String id; private final byte[] key; - private final String name; - private final String host; + + private final NodeProfile nodeProfile; private ClientState client; public Node( String id, byte[] key, - String name, - String host + NodeProfile nodeProfile ) { this.id = id; this.key = key; - this.name = name; - this.host = host; - } - - void setClient(ClientState client) { - this.client = client; + this.nodeProfile = nodeProfile; } ClientState getClient() { return client; } - boolean connected() { + NodeProfile getNodeProfile() { + return nodeProfile; + } + + void setClient(ClientState client) { + this.client = client; + } + + boolean isConnected() { return client != null; } - void send(Packet packet) { + void send(Packet packet) throws IOException { client.send(packet); } - static Node fromProperties(String id, Properties properties) { + static Node fromProperties(String id, Properties properties) throws UnknownHostException { + var ipv4 = properties.getProperty("stack.ipv4"); + var ipv6 = properties.getProperty("stack.ipv6"); + + Inet6Address linkLocal = (Inet6Address) InetAddress.getByName(properties.getProperty("link-local")); + if (!AddressTools.isLinkLocal(linkLocal)) throw new UnknownHostException(); + return new Node( id, Base64.getDecoder().decode(properties.getProperty("key")), - properties.getProperty("name"), - properties.getProperty("host") + new NodeProfile( + properties.getProperty("label"), + properties.getProperty("hostname"), + linkLocal, + ipv4.equalsIgnoreCase("yes") || ipv4.equalsIgnoreCase("restricted"), + ipv4.equalsIgnoreCase("restricted"), + ipv6.equalsIgnoreCase("yes") || ipv6.equalsIgnoreCase("restricted"), + ipv6.equalsIgnoreCase("restricted") + ) ); } @@ -60,37 +86,48 @@ public final class Node { return key; } - public String name() { - return name; - } - - public String host() { - return host; - } - @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null || obj.getClass() != this.getClass()) return false; var that = (Node) obj; return Objects.equals(this.id, that.id) && - Objects.equals(this.key, that.key) && - Objects.equals(this.name, that.name) && - Objects.equals(this.host, that.host); + Objects.equals(this.key, that.key); } @Override public int hashCode() { - return Objects.hash(id, key, name, host); + return Objects.hash(id, key); } @Override public String toString() { - return "Node[" + - "id=" + id + ", " + - "key=" + key + ", " + - "name=" + name + ", " + - "host=" + host + ']'; + return "Node[id=" + id + "]"; + } + + /* */ + + + CompletableFuture ping(InetAddress host) throws IOException { + var future = new CompletableFuture(); + + var id = (short) (ThreadLocalRandom.current().nextInt() & 0xFFFF); + + client.pingConsumers.put(id, future::complete); + send(new PingRequestPacket(id, host)); + + return future; + } + + CompletableFuture session(long asn, InetAddress linkLocal, String publicKey, String endpointHost, int endpointPort) throws IOException { + var future = new CompletableFuture(); + + client.sessionConsumers.put(asn, future::complete); + send(new SessionRequestPacket( + asn, linkLocal, publicKey, endpointHost, endpointPort + )); + + return future; } } diff --git a/src/main/java/eu/m724/autopeerer/server/PacketHandler.java b/src/main/java/eu/m724/autopeerer/server/PacketHandler.java index 71be9f2..73dfc88 100644 --- a/src/main/java/eu/m724/autopeerer/server/PacketHandler.java +++ b/src/main/java/eu/m724/autopeerer/server/PacketHandler.java @@ -5,7 +5,9 @@ import eu.m724.autopeerer.common.packet.Packets; import eu.m724.autopeerer.common.packet.c2s.LoginPacket; import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket; import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket; +import eu.m724.autopeerer.common.packet.s2c.LoginResponsePacket; +import java.io.IOException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.*; @@ -17,7 +19,7 @@ public class PacketHandler { nodes.forEach(n -> this.nodes.put(n, null)); } - void handle(ClientState state, ByteBuffer bytes) { + void handle(ClientState state, ByteBuffer bytes) throws IOException { // TODO this is not safe but enough for now Packet p; try { @@ -60,22 +62,24 @@ public class PacketHandler { .ifPresent(e -> nodes.remove(e.getKey())); } - private void handleLogin(ClientState state, LoginPacket packet) { + private void handleLogin(ClientState state, LoginPacket packet) throws IOException { var node = nodes.entrySet().stream().filter(e -> Arrays.equals(e.getKey().key(), packet.key)).findFirst().orElse(null); if (node != null) { if (node.getValue() != null) { - if (node.getValue().socket.isOpen()) { // if there WAS a connection + if (node.getValue().socket.isConnected()) { // if there WAS a connection System.out.printf("[%d] Tried to log in as %s, but is already logged in\n", state.clientId, node.getKey().id()); state.end(); return; } } - System.out.printf("[%d] Logged in as %s\n", state.clientId, node.getKey().id()); nodes.put(node.getKey(), state); node.getKey().setClient(state); state.authenticated = true; state.node = node.getKey(); + + System.out.printf("[%d] Logged in as %s\n", state.clientId, node.getKey().id()); + state.send(new LoginResponsePacket(node.getKey().id(), node.getKey().getNodeProfile().linkLocal())); } else { System.out.printf("[%d] Tried to log in with invalid key\n", state.clientId); state.end(); diff --git a/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java b/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java index c05a563..6e5ac9a 100644 --- a/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java +++ b/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java @@ -7,6 +7,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashSet; import java.util.Properties; import java.util.Set; @@ -21,9 +22,9 @@ public class ServerConfiguration extends Configuration { if (!nd.exists()) { nd.mkdir(); - var f = new File(nd, "node1.properties"); - try (var is = getClass().getClassLoader().getResourceAsStream(f.getName())) { - Files.write(f.toPath(), is.readAllBytes()); + + try (var is = getClass().getClassLoader().getResourceAsStream("nodes/node1.properties")) { + Files.write(Path.of(nd.getPath(),"node1.properties"), is.readAllBytes()); } } diff --git a/src/main/resources/client.properties b/src/main/resources/client.properties index 1a7d92b..eafbaf3 100644 --- a/src/main/resources/client.properties +++ b/src/main/resources/client.properties @@ -1,12 +1,8 @@ -# The server websocket -remote=ws://127.0.0.1:8002 +remote=127.0.0.1:8002 +key=AAAAAAAAAAA= + # Where are WG and BIRD configs located wireguard.directory=config/wg bird.directory=config/bird -# Link local used for peering -link-local=fe80::129:0 -# Is IPv4 available? Yes, no, restricted -stack.ipv4=yes -# Is IPv6 available? Yes, no, restricted -stack.ipv6=yes \ No newline at end of file +# Other stuff is configured on the server \ No newline at end of file diff --git a/src/main/resources/nodes/node1.properties b/src/main/resources/nodes/node1.properties index 6ec89e4..b2f4a88 100644 --- a/src/main/resources/nodes/node1.properties +++ b/src/main/resources/nodes/node1.properties @@ -1,3 +1,10 @@ key=AAAAAAAAAAA= -name="Node Number One" -host=example.com \ No newline at end of file +label="Node Number One" +# The hostname used as WireGuard endpoint +hostname=example.com +# Link local used for peering +link-local=fe80::129:0 +# Is IPv4 available? Yes, no, restricted +stack.ipv4=yes +# Is IPv6 available? Yes, no, restricted +stack.ipv6=yes \ No newline at end of file