From b3752fd4aeb8803ebe0e64e14e7f03faa917e7fa Mon Sep 17 00:00:00 2001
From: Minecon724 <git@m724.eu>
Date: Thu, 26 Dec 2024 20:54:23 +0100
Subject: [PATCH] TCP

---
 config/nodes/node1.properties                 |   3 -
 pom.xml                                       |   5 -
 .../m724/autopeerer/client/ClientPrefs.java   |  45 --------
 .../java/eu/m724/autopeerer/client/Main.java  |  36 ++++--
 .../m724/autopeerer/client/MyTcpClient.java   |  64 +++++++++++
 .../autopeerer/client/MyWebsocketClient.java  |  58 ----------
 .../m724/autopeerer/client/PacketHandler.java |  82 ++++++++++----
 .../m724/autopeerer/client/bird/BirdLive.java |   5 +
 .../client/connectivity/Connectivity.java     |  39 +++++++
 .../client/wireguard/WireGuardLive.java       |   5 +
 .../m724/autopeerer/common/AddressTools.java  |   4 +-
 .../m724/autopeerer/common/NodeProfile.java   |  14 +++
 .../common/packet/PacketReaderImpl.java       |   9 ++
 .../common/packet/PacketReaderRunnable.java   |  38 +++++++
 .../autopeerer/common/packet/Packets.java     |  15 ++-
 .../common/packet/c2s/LoginPacket.java        |   5 +
 .../packet/s2c/LoginResponsePacket.java       |  29 ++++-
 .../m724/autopeerer/server/ClientState.java   |  48 ++------
 .../java/eu/m724/autopeerer/server/Main.java  |   2 +-
 .../m724/autopeerer/server/MyHttpHandler.java |  16 ++-
 .../m724/autopeerer/server/MyTcpServer.java   |  84 ++++++++++++++
 .../autopeerer/server/MyWebsocketServer.java  |  59 ----------
 .../java/eu/m724/autopeerer/server/Node.java  | 103 ++++++++++++------
 .../m724/autopeerer/server/PacketHandler.java |  12 +-
 .../server/ServerConfiguration.java           |   7 +-
 src/main/resources/client.properties          |  12 +-
 src/main/resources/nodes/node1.properties     |  11 +-
 27 files changed, 504 insertions(+), 306 deletions(-)
 delete mode 100644 config/nodes/node1.properties
 delete mode 100644 src/main/java/eu/m724/autopeerer/client/ClientPrefs.java
 create mode 100644 src/main/java/eu/m724/autopeerer/client/MyTcpClient.java
 delete mode 100644 src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java
 create mode 100644 src/main/java/eu/m724/autopeerer/client/connectivity/Connectivity.java
 create mode 100644 src/main/java/eu/m724/autopeerer/common/NodeProfile.java
 create mode 100644 src/main/java/eu/m724/autopeerer/common/packet/PacketReaderImpl.java
 create mode 100644 src/main/java/eu/m724/autopeerer/common/packet/PacketReaderRunnable.java
 create mode 100644 src/main/java/eu/m724/autopeerer/server/MyTcpServer.java
 delete mode 100644 src/main/java/eu/m724/autopeerer/server/MyWebsocketServer.java

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 @@
     </properties>
 
     <dependencies>
-        <dependency>
-            <groupId>org.java-websocket</groupId>
-            <artifactId>Java-WebSocket</artifactId>
-            <version>1.5.7</version>
-        </dependency>
         <dependency>
             <groupId>org.json</groupId>
             <artifactId>json</artifactId>
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<String>();
+
+        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<ByteBuffer> 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<Boolean> 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<LoginPacket> {
@@ -21,6 +25,7 @@ public class LoginPacket implements Packet<LoginPacket> {
     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<LoginResponsePacket> {
     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<LoginResponsePacket> {
     @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<LoginResponsePacket> {
         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<ByteBuffer> 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<PingResponsePacket> ping(InetAddress host) {
-        var future = new CompletableFuture<PingResponsePacket>();
-
-        var id = (short) (ThreadLocalRandom.current().nextInt() & 0xFFFF);
-
-        pingConsumers.put(id, future::complete);
-        send(new PingRequestPacket(id, host));
-
-        return future;
-    }
-
-    CompletableFuture<SessionResponsePacket> session(long asn, InetAddress linkLocal, String publicKey, String endpointHost, int endpointPort) {
-        var future = new CompletableFuture<SessionResponsePacket>();
-
-        sessionConsumers.put(asn, future::complete);
-        send(new SessionRequestPacket(
-                asn, linkLocal, publicKey, endpointHost, endpointPort
-        ));
-
-        return future;
-    }
 
 
     /* Packet functions */
 
-    private final Map<Short, Consumer<PingResponsePacket>> pingConsumers = new HashMap<>();
-    private final Map<Long, Consumer<SessionResponsePacket>> sessionConsumers = new HashMap<>();
+    final Map<Short, Consumer<PingResponsePacket>> pingConsumers = new HashMap<>();
+    final Map<Long, Consumer<SessionResponsePacket>> 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<CompletableFuture<Void>>();
 
                 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<SessionResponsePacket> 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<Integer, ClientState> 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<WebSocket, ClientState> 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<PingResponsePacket> ping(InetAddress host) throws IOException {
+        var future = new CompletableFuture<PingResponsePacket>();
+
+        var id = (short) (ThreadLocalRandom.current().nextInt() & 0xFFFF);
+
+        client.pingConsumers.put(id, future::complete);
+        send(new PingRequestPacket(id, host));
+
+        return future;
+    }
+
+    CompletableFuture<SessionResponsePacket> session(long asn, InetAddress linkLocal, String publicKey, String endpointHost, int endpointPort) throws IOException {
+        var future = new CompletableFuture<SessionResponsePacket>();
+
+        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