diff --git a/pom.xml b/pom.xml
index b19f677..ed4b8d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,5 +20,41 @@
Java-WebSocket
1.5.7
+
+ com.github.seancfoley
+ ipaddress
+ 5.5.1
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+
+ package
+
+ shade
+
+
+ false
+ true
+
+
+ **
+
+ module-info.class
+ META-INF/
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java b/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java
index 93e69f5..bc94612 100644
--- a/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java
+++ b/src/main/java/eu/m724/autopeerer/client/MyWebsocketClient.java
@@ -1,7 +1,10 @@
package eu.m724.autopeerer.client;
import eu.m724.autopeerer.packet.Packets;
-import eu.m724.autopeerer.packet.PingRequestPacket;
+import eu.m724.autopeerer.packet.client.PingRequestPacket;
+import eu.m724.autopeerer.packet.client.VpnRequestPacket;
+import inet.ipaddr.IPAddressString;
+import inet.ipaddr.ipv6.IPv6Address;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
@@ -32,9 +35,10 @@ public class MyWebsocketClient extends WebSocketClient {
System.out.printf("Connected in %.3f ms\n", connectTime);
try {
- for (int i=0; i<10; i++) {
- send(Packets.compose(new PingRequestPacket((short)i, InetAddress.getByName("1.1.1." + i))));
- }
+ send(Packets.compose(new PingRequestPacket((short)1, InetAddress.getByName("1.1.1.3"))));
+ send(Packets.compose(new PingRequestPacket((short)2, InetAddress.getByName("1.1.1.1"))));
+
+ send(Packets.compose(new VpnRequestPacket((short)1, (IPv6Address) new IPAddressString("fefe::fefe").getAddress(), "sAt8JSXW4leihcAAdsghsfgFWkO5stBZJm87PGLZFXY=", "example.com", 6823)));
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
diff --git a/src/main/java/eu/m724/autopeerer/client/PacketHandler.java b/src/main/java/eu/m724/autopeerer/client/PacketHandler.java
index 96addd1..6ed08cd 100644
--- a/src/main/java/eu/m724/autopeerer/client/PacketHandler.java
+++ b/src/main/java/eu/m724/autopeerer/client/PacketHandler.java
@@ -1,9 +1,10 @@
package eu.m724.autopeerer.client;
-import eu.m724.autopeerer.packet.Packet;
-import eu.m724.autopeerer.packet.Packets;
-import eu.m724.autopeerer.packet.PingRequestPacket;
-import eu.m724.autopeerer.packet.PingResponsePacket;
+import eu.m724.autopeerer.client.wireguard.WireGuardSession;
+import eu.m724.autopeerer.packet.*;
+import eu.m724.autopeerer.packet.client.PingRequestPacket;
+import eu.m724.autopeerer.packet.client.VpnRequestPacket;
+import eu.m724.autopeerer.packet.server.PingResponsePacket;
import java.io.BufferedReader;
import java.io.IOException;
@@ -34,41 +35,52 @@ public class PacketHandler {
throw new RuntimeException(e);
}
- if (p instanceof PingRequestPacket packet) {System.out.printf("Ping request #%d to %s\n", packet.requestId, packet.target.getHostAddress());
-
- CompletableFuture.runAsync(() -> {
- float average = -1, meanDeviation = -1;
- var status = PingResponsePacket.PingResponseStatus.ERROR;
-
- try {
- Process process = Runtime.getRuntime().exec(new String[] { "ping", "-3Ac", "10", packet.target.getHostAddress() });
-
- try (BufferedReader reader = process.inputReader()) {
- for (String l : reader.lines().toList()) {
- boolean error = l.startsWith("From"); // indicates an error
- boolean end = l.startsWith("rtt");
-
- if (error) {
- status = PingResponsePacket.PingResponseStatus.UNREACHABLE;
- break;
- }
-
- if (end) {
- String[] results = l.split(" ")[3].split("/");
- average = Float.parseFloat(results[1]);
- meanDeviation = Float.parseFloat(results[3]);
- status = PingResponsePacket.PingResponseStatus.OK;
- }
- }
- }
- } catch (IOException e) {
- System.err.println("Error pinging");
- 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);
- sender.accept(new PingResponsePacket(packet.requestId, status, average, meanDeviation).serialize());
- });
+ if (p instanceof PingRequestPacket packet) {
+ handlePingRequest(packet);
+ } else if (p instanceof VpnRequestPacket packet) {
+ handleVpnRequest(packet);
}
}
+
+ private void handlePingRequest(PingRequestPacket packet) {
+ System.out.printf("Ping request #%d to %s\n", packet.requestId, packet.target.getHostAddress());
+ CompletableFuture.runAsync(() -> {
+ float average = -1, meanDeviation = -1;
+ var status = PingResponsePacket.PingResponseStatus.ERROR;
+
+ try {
+ Process process = Runtime.getRuntime().exec(new String[] { "ping", "-3Ac", "10", packet.target.getHostAddress() });
+
+ try (BufferedReader reader = process.inputReader()) {
+ for (String l : reader.lines().toList()) {
+ boolean error = l.startsWith("From"); // indicates an error
+ boolean end = l.startsWith("rtt");
+
+ if (error) {
+ status = PingResponsePacket.PingResponseStatus.UNREACHABLE;
+ break;
+ }
+
+ if (end) {
+ String[] results = l.split(" ")[3].split("/");
+ average = Float.parseFloat(results[1]);
+ meanDeviation = Float.parseFloat(results[3]);
+ status = PingResponsePacket.PingResponseStatus.OK;
+ }
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("Error pinging");
+ 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);
+ sender.accept(new PingResponsePacket(packet.requestId, status, average, meanDeviation).serialize());
+ });
+ }
+
+ private void handleVpnRequest(VpnRequestPacket packet) {
+ var session = new WireGuardSession(12345, "serverpoecjteta", "fefe:fefe::fefe", packet.linkLocal.toCompressedString(), packet.endpointHost + ":" + packet.endpointPort, packet.publicKey);
+ System.err.println(session.config());
+ }
}
diff --git a/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardKeys.java b/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardKeys.java
new file mode 100644
index 0000000..01ef72a
--- /dev/null
+++ b/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardKeys.java
@@ -0,0 +1,34 @@
+package eu.m724.autopeerer.client.wireguard;
+
+import java.io.IOException;
+
+public class WireGuardKeys {
+ public static String generatePrivateKey() {
+ try {
+ Process process = Runtime.getRuntime().exec("wg genkey");
+ process.waitFor();
+ return process.inputReader().readLine();
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Derive a WireGuard public key from a private key
+ * @param privateKey base64 encoded private key
+ * @return base64 encoded public key or null if invalid private key
+ */
+ public static String derivePublicKey(String privateKey) {
+ try {
+ Process process = Runtime.getRuntime().exec("wg genkey");
+ process.outputWriter().write(privateKey);
+
+ int code = process.waitFor();
+ if (code != 0) return null;
+
+ return process.inputReader().readLine();
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardSession.java b/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardSession.java
new file mode 100644
index 0000000..fb3d179
--- /dev/null
+++ b/src/main/java/eu/m724/autopeerer/client/wireguard/WireGuardSession.java
@@ -0,0 +1,25 @@
+package eu.m724.autopeerer.client.wireguard;
+
+public record WireGuardSession(
+ int listenPort,
+ String serverPrivateKey,
+ String localLinkLocal,
+ String clientLinkLocal,
+ String endpoint,
+ String clientPublicKey
+) {
+ public String config() {
+ return """
+ [Interface]
+ ListenPort = %d
+ PrivateKey = %s
+ PostUp = /sbin/ip addr add dev %%i %s peer %s
+ Table = off
+
+ [Peer]
+ Endpoint = %s
+ PublicKey = %s
+ AllowedIPs = ::/0"""
+ .formatted(listenPort, serverPrivateKey, localLinkLocal, clientLinkLocal, endpoint, clientPublicKey);
+ }
+}
diff --git a/src/main/java/eu/m724/autopeerer/packet/Packets.java b/src/main/java/eu/m724/autopeerer/packet/Packets.java
index 31b9eaa..3fddb55 100644
--- a/src/main/java/eu/m724/autopeerer/packet/Packets.java
+++ b/src/main/java/eu/m724/autopeerer/packet/Packets.java
@@ -1,5 +1,10 @@
package eu.m724.autopeerer.packet;
+import eu.m724.autopeerer.packet.client.PingRequestPacket;
+import eu.m724.autopeerer.packet.client.VpnRequestPacket;
+import eu.m724.autopeerer.packet.server.PingResponsePacket;
+import eu.m724.autopeerer.packet.server.VpnResponsePacket;
+
import java.nio.ByteBuffer;
public class Packets {
@@ -10,6 +15,8 @@ public class Packets {
if (id == 1) {
packet = PingRequestPacket.deserialize(buffer);
+ } else if (id == 2) {
+ packet = VpnRequestPacket.deserialize(buffer);
}
return packet;
@@ -21,7 +28,9 @@ public class Packets {
Packet> packet = null;
if (id == 1) {
- PingResponsePacket.deserialize(buffer);
+ packet = PingResponsePacket.deserialize(buffer);
+ } else if (id == 2) {
+ packet = VpnResponsePacket.deserialize(buffer);
}
return packet;
diff --git a/src/main/java/eu/m724/autopeerer/packet/client/BgpRequestPacket.java b/src/main/java/eu/m724/autopeerer/packet/client/BgpRequestPacket.java
new file mode 100644
index 0000000..9481e09
--- /dev/null
+++ b/src/main/java/eu/m724/autopeerer/packet/client/BgpRequestPacket.java
@@ -0,0 +1,43 @@
+package eu.m724.autopeerer.packet.client;
+
+import eu.m724.autopeerer.packet.Packet;
+import inet.ipaddr.ipv6.IPv6Address;
+
+import java.nio.ByteBuffer;
+
+public class BgpRequestPacket implements Packet {
+ public final short id;
+ public final long asn;
+ public final IPv6Address linkLocal;
+
+ public BgpRequestPacket(short id, long asn, IPv6Address linkLocal) {
+ this.id = id;
+ this.asn = asn;
+ this.linkLocal = linkLocal;
+ }
+
+ @Override
+ public byte getId() {
+ return 3;
+ }
+
+ @Override
+ public ByteBuffer serialize() {
+ ByteBuffer buffer = ByteBuffer.allocate(22);
+ buffer.putShort(id);
+ buffer.putInt((int) (asn & 0xFFFFFFFFL));
+ buffer.put(linkLocal.getBytes());
+ return buffer;
+ }
+
+ public static BgpRequestPacket deserialize(ByteBuffer buffer) {
+ var id = buffer.getShort();
+ var asn = Integer.toUnsignedLong(buffer.getInt());
+
+ var ip = new byte[16];
+ buffer.get(ip);
+ var linkLocal = new IPv6Address(ip);
+
+ return new BgpRequestPacket(id, asn, linkLocal);
+ }
+}
diff --git a/src/main/java/eu/m724/autopeerer/packet/PingRequestPacket.java b/src/main/java/eu/m724/autopeerer/packet/client/PingRequestPacket.java
similarity index 93%
rename from src/main/java/eu/m724/autopeerer/packet/PingRequestPacket.java
rename to src/main/java/eu/m724/autopeerer/packet/client/PingRequestPacket.java
index 5c29024..c4c337f 100644
--- a/src/main/java/eu/m724/autopeerer/packet/PingRequestPacket.java
+++ b/src/main/java/eu/m724/autopeerer/packet/client/PingRequestPacket.java
@@ -1,4 +1,6 @@
-package eu.m724.autopeerer.packet;
+package eu.m724.autopeerer.packet.client;
+
+import eu.m724.autopeerer.packet.Packet;
import java.net.Inet6Address;
import java.net.InetAddress;
diff --git a/src/main/java/eu/m724/autopeerer/packet/client/VpnRequestPacket.java b/src/main/java/eu/m724/autopeerer/packet/client/VpnRequestPacket.java
new file mode 100644
index 0000000..4023668
--- /dev/null
+++ b/src/main/java/eu/m724/autopeerer/packet/client/VpnRequestPacket.java
@@ -0,0 +1,80 @@
+package eu.m724.autopeerer.packet.client;
+
+import eu.m724.autopeerer.packet.Packet;
+import inet.ipaddr.HostName;
+import inet.ipaddr.HostNameException;
+import inet.ipaddr.IPAddressString;
+import inet.ipaddr.ipv6.IPv6Address;
+
+import java.net.IDN;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+public class VpnRequestPacket implements Packet {
+ public final short connectionId;
+ public final IPv6Address linkLocal;
+ public final String publicKey;
+ public final String endpointHost;
+ public final int endpointPort;
+
+ public VpnRequestPacket(short connectionId, IPv6Address linkLocal, String publicKey, String endpointHost, int endpointPort) {
+ this.connectionId = connectionId;
+
+ this.linkLocal = linkLocal;
+ assert new IPAddressString("fe80::/10").getAddress().contains(linkLocal);
+
+ this.publicKey = publicKey;
+ assert Base64.getDecoder().decode(publicKey).length == 32;
+
+ this.endpointHost = IDN.toASCII(endpointHost, IDN.ALLOW_UNASSIGNED);
+ try {
+ new HostName(endpointHost).validate();
+ } catch (HostNameException e) {
+ throw new RuntimeException(e);
+ }
+
+ this.endpointPort = endpointPort;
+ assert endpointPort > 0 && endpointPort < 65536;
+ }
+
+ @Override
+ public byte getId() {
+ return 2;
+ }
+
+ public static VpnRequestPacket deserialize(ByteBuffer buffer) throws Exception {
+ var id = buffer.getShort();
+
+ var ll = new byte[16];
+ buffer.get(ll);
+ var linkLocal = new IPv6Address(ll);
+
+ var pk = new byte[32];
+ buffer.get(pk);
+ var publicKey = Base64.getEncoder().encodeToString(pk);
+
+ var endpointPort = buffer.getShort();
+
+ var epl = buffer.get() & 0xFF;
+ var ep = new byte[epl];
+ buffer.get(ep);
+ var endpointHost = new String(ep, StandardCharsets.US_ASCII);
+
+ return new VpnRequestPacket(id, linkLocal, publicKey, endpointHost, endpointPort);
+ }
+
+ @Override
+ public ByteBuffer serialize() {
+ var buffer = ByteBuffer.allocate(53 + endpointHost.length());
+
+ buffer.putShort(connectionId); // 2b
+ buffer.put(linkLocal.getBytes()); // 16b
+ buffer.put(Base64.getDecoder().decode(publicKey)); // 32b
+ buffer.putShort((short)endpointPort); // 2b
+ buffer.put((byte) endpointHost.length()); // 1b
+ buffer.put(endpointHost.getBytes(StandardCharsets.US_ASCII));
+
+ return buffer;
+ }
+}
diff --git a/src/main/java/eu/m724/autopeerer/packet/server/BgpResponsePacket.java b/src/main/java/eu/m724/autopeerer/packet/server/BgpResponsePacket.java
new file mode 100644
index 0000000..5975ab2
--- /dev/null
+++ b/src/main/java/eu/m724/autopeerer/packet/server/BgpResponsePacket.java
@@ -0,0 +1,37 @@
+package eu.m724.autopeerer.packet.server;
+
+import eu.m724.autopeerer.packet.Packet;
+
+import java.nio.ByteBuffer;
+
+public class BgpResponsePacket implements Packet {
+ public final short id;
+ public final boolean success;
+
+ public BgpResponsePacket(short id, boolean success) {
+ this.id = id;
+ this.success = success;
+ }
+
+ @Override
+ public byte getId() {
+ return 3;
+ }
+
+ @Override
+ public ByteBuffer serialize() {
+ ByteBuffer buffer = ByteBuffer.allocate(3);
+
+ buffer.putShort(id);
+ buffer.put((byte) (success ? 1 : 0));
+
+ return buffer;
+ }
+
+ public static BgpResponsePacket deserialize(ByteBuffer buffer) {
+ var id = buffer.getShort();
+ var success = buffer.get() == 1;
+
+ return new BgpResponsePacket(id, success);
+ }
+}
diff --git a/src/main/java/eu/m724/autopeerer/packet/PingResponsePacket.java b/src/main/java/eu/m724/autopeerer/packet/server/PingResponsePacket.java
similarity index 95%
rename from src/main/java/eu/m724/autopeerer/packet/PingResponsePacket.java
rename to src/main/java/eu/m724/autopeerer/packet/server/PingResponsePacket.java
index a27c000..478f311 100644
--- a/src/main/java/eu/m724/autopeerer/packet/PingResponsePacket.java
+++ b/src/main/java/eu/m724/autopeerer/packet/server/PingResponsePacket.java
@@ -1,4 +1,6 @@
-package eu.m724.autopeerer.packet;
+package eu.m724.autopeerer.packet.server;
+
+import eu.m724.autopeerer.packet.Packet;
import java.nio.ByteBuffer;
diff --git a/src/main/java/eu/m724/autopeerer/packet/server/VpnResponsePacket.java b/src/main/java/eu/m724/autopeerer/packet/server/VpnResponsePacket.java
new file mode 100644
index 0000000..2901db9
--- /dev/null
+++ b/src/main/java/eu/m724/autopeerer/packet/server/VpnResponsePacket.java
@@ -0,0 +1,48 @@
+package eu.m724.autopeerer.packet.server;
+
+import eu.m724.autopeerer.packet.Packet;
+
+import java.nio.ByteBuffer;
+import java.util.Base64;
+
+public class VpnResponsePacket implements Packet {
+ public final short connectionId;
+ public final boolean success;
+ public final int port;
+ public final String publicKey;
+
+ public VpnResponsePacket(short connectionId, boolean success, int port, String publicKey) {
+ this.connectionId = connectionId;
+ this.success = success;
+ this.port = port;
+ this.publicKey = publicKey;
+ }
+
+ @Override
+ public byte getId() {
+ return 2;
+ }
+
+ public static VpnResponsePacket deserialize(ByteBuffer buffer) throws Exception {
+ var id = buffer.getShort();
+ var port = buffer.getShort() & 0xFFFF;
+ var success = port != 0;
+
+ var pkb = new byte[32];
+ buffer.get(pkb);
+ var publicKey = Base64.getEncoder().encodeToString(pkb);
+
+ return new VpnResponsePacket(id, success, port, publicKey);
+ }
+
+ @Override
+ public ByteBuffer serialize() {
+ var buffer = ByteBuffer.allocate(35);
+
+ buffer.putShort(connectionId); // 2b
+ buffer.put((byte) (success ? 1 : 0));
+ buffer.put(Base64.getDecoder().decode(publicKey));
+
+ return buffer;
+ }
+}