A
All checks were successful
/ deploy (push) Successful in 31s

This commit is contained in:
Minecon724 2024-12-23 15:24:31 +01:00
parent a5d860cb35
commit a19fa8f2fa
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
14 changed files with 371 additions and 44 deletions

View file

@ -25,6 +25,11 @@
<artifactId>ipaddress</artifactId> <artifactId>ipaddress</artifactId>
<version>5.5.1</version> <version>5.5.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View file

@ -5,10 +5,10 @@ import eu.m724.autopeerer.client.wireguard.WireGuardLive;
import eu.m724.autopeerer.client.wireguard.WireGuardSession; import eu.m724.autopeerer.client.wireguard.WireGuardSession;
import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packet;
import eu.m724.autopeerer.common.packet.Packets; import eu.m724.autopeerer.common.packet.Packets;
import eu.m724.autopeerer.common.packet.c2s.PingRequestPacket; import eu.m724.autopeerer.common.packet.s2c.PingRequestPacket;
import eu.m724.autopeerer.common.packet.c2s.SessionRequestPacket; import eu.m724.autopeerer.common.packet.s2c.SessionRequestPacket;
import eu.m724.autopeerer.common.packet.s2c.PingResponsePacket; import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket;
import eu.m724.autopeerer.common.packet.s2c.SessionResponsePacket; import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;

View file

@ -1,11 +1,11 @@
package eu.m724.autopeerer.common.packet; package eu.m724.autopeerer.common.packet;
import eu.m724.autopeerer.common.packet.c2s.LoginPacket; import eu.m724.autopeerer.common.packet.c2s.LoginPacket;
import eu.m724.autopeerer.common.packet.c2s.PingRequestPacket; import eu.m724.autopeerer.common.packet.s2c.PingRequestPacket;
import eu.m724.autopeerer.common.packet.c2s.SessionRequestPacket; import eu.m724.autopeerer.common.packet.s2c.SessionRequestPacket;
import eu.m724.autopeerer.common.packet.s2c.LoginResponsePacket; import eu.m724.autopeerer.common.packet.s2c.LoginResponsePacket;
import eu.m724.autopeerer.common.packet.s2c.PingResponsePacket; import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket;
import eu.m724.autopeerer.common.packet.s2c.SessionResponsePacket; import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.function.Consumer; import java.util.function.Consumer;

View file

@ -1,4 +1,4 @@
package eu.m724.autopeerer.common.packet.s2c; package eu.m724.autopeerer.common.packet.c2s;
import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packet;

View file

@ -1,4 +1,4 @@
package eu.m724.autopeerer.common.packet.s2c; package eu.m724.autopeerer.common.packet.c2s;
import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packet;

View file

@ -1,4 +1,4 @@
package eu.m724.autopeerer.common.packet.c2s; package eu.m724.autopeerer.common.packet.s2c;
import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packet;

View file

@ -1,4 +1,4 @@
package eu.m724.autopeerer.common.packet.c2s; package eu.m724.autopeerer.common.packet.s2c;
import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packet;
import inet.ipaddr.HostName; import inet.ipaddr.HostName;

View file

@ -2,9 +2,19 @@ package eu.m724.autopeerer.server;
import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packet;
import eu.m724.autopeerer.common.packet.Packets; 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 org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer; import java.util.function.Consumer;
public class ClientState { public class ClientState {
@ -28,4 +38,38 @@ public class ClientState {
void end() { void end() {
socket.close(); 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;
}
/* Packet functions */
private Map<Short, Consumer<PingResponsePacket>> pingConsumers = new HashMap<>();
void onPacketReceived(Packet<?> p) {
if (p instanceof PingResponsePacket packet) {
handlePingResponse(packet);
} else if (p instanceof SessionResponsePacket packet) {
handleSessionResponse(packet);
}
}
private void handlePingResponse(PingResponsePacket packet) {
System.out.printf("[%d] Ping response #%d: %s avg %.3f / mdev %.3f ms\n", clientId, packet.requestId, packet.status, packet.average, packet.meanDeviation);
pingConsumers.remove(packet.requestId).accept(packet);
}
private void handleSessionResponse(SessionResponsePacket packet) {
System.out.printf("[%d] Session response #%d: %s\n", clientId, packet.sessionId, packet.result);
}
} }

View file

@ -1,11 +1,13 @@
package eu.m724.autopeerer.server; package eu.m724.autopeerer.server;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Set; import java.util.Set;
public class Main { public class Main {
public static void main(String[] args) throws InterruptedException { public static void main(String[] args) throws IOException {
System.out.println("Hello world!"); System.out.println("Hello world!");
var config = new ServerConfiguration(); var config = new ServerConfiguration();
@ -23,10 +25,21 @@ public class Main {
var packetHandler = new PacketHandler(nodes); var packetHandler = new PacketHandler(nodes);
var server = new MyWebsocketServer( var server = new MyWebsocketServer(
new InetSocketAddress(config.getString("listen.address"), config.getInt("listen.port")), new InetSocketAddress(config.getString("socket.address"), config.getInt("socket.port")),
packetHandler packetHandler
); );
server.start(); server.start();
HttpServer httpServer = HttpServer.create(
new InetSocketAddress(config.getString("http.address"), config.getInt("http.port")),
0
);
httpServer.createContext("/", new MyHttpHandler(nodes, config.getString("http.key")));
httpServer.setExecutor(null);
httpServer.start();
System.out.println("HTTP server started on " + httpServer.getAddress().getHostString() + ":" + httpServer.getAddress().getPort());
} }
} }

View file

@ -0,0 +1,195 @@
package eu.m724.autopeerer.server;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class MyHttpHandler implements HttpHandler {
private final Set<Node> nodes;
private final String key;
public MyHttpHandler(Set<Node> nodes, String key) {
this.nodes = nodes;
this.key = key;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
String key = exchange.getRequestHeaders().getFirst("key");
if (!this.key.equals(key)) {
exchange.sendResponseHeaders(403, -1);
return;
}
try {
inner(exchange);
} catch (IOException e) {
exchange.sendResponseHeaders(500, -1);
} catch (EndException e) { }
}
private void inner(HttpExchange exchange) throws IOException, EndException {
String[] path = exchange.getRequestURI().getPath().substring(1).split("/");
if (path.length == 0) {
sendResponse(exchange, 200, "Hello");
} else {
if (path[0].equals("nodes")) {
requireMethod(exchange, "GET");
var json = new JSONObject();
nodes.forEach(node -> {
var val = new JSONObject()
.put("name", node.name())
.put("host", node.host())
.put("online", node.connected());
json.put(node.id(), val);
});
sendResponse(exchange, 200, json);
} else if (path[0].equals("ping")) {
requireMethod(exchange, "POST");
var json = getJsonBody(exchange);
Set<Node> selectedNodes = this.nodes;
if (json.has("node")) {
selectedNodes = nodes.stream()
.filter(n -> n.id().equals(json.getString("node")))
.collect(Collectors.toSet());
if (selectedNodes.isEmpty()) // no node with that ID found
sendResponse(exchange, 400, "node");
}
InetAddress target = null;
try {
target = InetAddress.getByName(json.getString("target"));
} catch (JSONException | UnknownHostException e) {
sendResponse(exchange, 400, "target");
}
sseStart(exchange);
var futures = new HashSet<CompletableFuture<Void>>();
for (Node node : selectedNodes) {
if (!node.connected()) { // node is offline
var response = new JSONObject()
.put("node", node.id())
.put("status", "OFFLINE");
sseWrite(exchange, response.toString());
continue;
}
var future = node.getClient().ping(target).handle((result, ex) -> {
try {
if (ex != null) {
sendResponse(exchange, 500);
}
var response = new JSONObject()
.put("node", node.id())
.put("status", result.status);
if (result.status == PingResponsePacket.PingResponseStatus.OK) {
response.put("average", result.average)
.put("meanDeviation", result.meanDeviation);
}
sseWrite(exchange, response.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (EndException e) { }
return (Void) null;
});
futures.add(future);
}
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).handle((v, ex) -> {
// TODO I don't know what to do with the errors
if (ex != null) {
ex.printStackTrace();
}
try {
sseClose(exchange);
} catch (IOException e) {
e.printStackTrace();
}
return null;
});
} else if (path[0].equals("peer")) {
}
}
}
private void sseStart(HttpExchange exchange) throws IOException {
exchange.getResponseHeaders().add("Content-Type", "text/event-stream; charset=utf-8");
exchange.sendResponseHeaders(200, 0); // 0 for chunked encoding
}
private void sseWrite(HttpExchange exchange, String message) throws IOException {
var body = exchange.getResponseBody();
body.write(("data: " + message + "\n\n").getBytes(StandardCharsets.UTF_8));
body.flush();
}
private void sseClose(HttpExchange exchange) throws IOException {
exchange.getResponseBody().close();
}
private JSONObject getJsonBody(HttpExchange exchange) throws IOException {
String body;
try (var is = exchange.getRequestBody()) {
body = new String(is.readAllBytes());
}
return new JSONObject(body);
}
private void requireMethod(HttpExchange exchange, String method) throws IOException, EndException {
if (!exchange.getRequestMethod().equals(method)) {
sendResponse(exchange, 405);
}
}
private void sendResponse(HttpExchange exchange, int statusCode) throws IOException, EndException {
exchange.sendResponseHeaders(statusCode, -1);
throw new EndException();
}
private void sendResponse(HttpExchange exchange, int statusCode, Object response) throws IOException, EndException {
var type = (response instanceof JSONObject || response instanceof JSONArray) ? "application/json; charset=utf8" : "text/plain; charset=utf8";
exchange.getResponseHeaders().add("Content-Type", type);
byte[] responseBytes = response.toString().getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(statusCode, responseBytes.length);
OutputStream outputStream = exchange.getResponseBody();
outputStream.write(responseBytes);
outputStream.close();
throw new EndException();
}
private static class EndException extends Exception { }
}

View file

@ -1,7 +1,7 @@
package eu.m724.autopeerer.server; package eu.m724.autopeerer.server;
import eu.m724.autopeerer.common.packet.c2s.PingRequestPacket; import eu.m724.autopeerer.common.packet.s2c.PingRequestPacket;
import eu.m724.autopeerer.common.packet.c2s.SessionRequestPacket; import eu.m724.autopeerer.common.packet.s2c.SessionRequestPacket;
import inet.ipaddr.IPAddressString; import inet.ipaddr.IPAddressString;
import org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshake;
@ -35,10 +35,9 @@ public class MyWebsocketServer extends WebSocketServer {
System.out.printf("[%d] Connected: %s\n", id, conn.getRemoteSocketAddress().getHostString()); System.out.printf("[%d] Connected: %s\n", id, conn.getRemoteSocketAddress().getHostString());
// TODO testing // TODO testing
try { /*state.send(new PingRequestPacket((short) 1, InetAddress.getByName("1.1.1.1")));
state.send(new PingRequestPacket((short) 1, InetAddress.getByName("1.1.1.1")));
state.send(new PingRequestPacket((short) 2, InetAddress.getByName("1.2.3.4"))); state.send(new PingRequestPacket((short) 2, InetAddress.getByName("1.2.3.4")));
state.send(new PingRequestPacket((short) 3, InetAddress.getByName("1.1.1.2"))); state.send(new PingRequestPacket((short) 3, InetAddress.getByName("1.1.1.2")));*/
state.send(new SessionRequestPacket( state.send(new SessionRequestPacket(
(short) 1, (short) 1,
@ -48,14 +47,12 @@ public class MyWebsocketServer extends WebSocketServer {
51820, 51820,
4242420000L 4242420000L
)); ));
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
} }
@Override @Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) { public void onClose(WebSocket conn, int code, String reason, boolean remote) {
var state = states.remove(conn); var state = states.remove(conn);
state.node.setClient(null);
var id = state.authenticated ? state.node.id() : conn.getRemoteSocketAddress().getHostString(); var id = state.authenticated ? state.node.id() : conn.getRemoteSocketAddress().getHostString();
System.out.printf("[%d] Disconnected: %s\n", state.clientId, id); System.out.printf("[%d] Disconnected: %s\n", state.clientId, id);
} }

View file

@ -1,14 +1,48 @@
package eu.m724.autopeerer.server; package eu.m724.autopeerer.server;
import eu.m724.autopeerer.common.packet.Packet;
import eu.m724.autopeerer.common.packet.Packets;
import java.util.Base64; import java.util.Base64;
import java.util.Objects;
import java.util.Properties; import java.util.Properties;
public record Node( public final class Node {
String id, private final String id;
byte[] key, private final byte[] key;
String name, private final String name;
String host private final String host;
) {
private ClientState client;
public Node(
String id,
byte[] key,
String name,
String host
) {
this.id = id;
this.key = key;
this.name = name;
this.host = host;
}
void setClient(ClientState client) {
this.client = client;
}
ClientState getClient() {
return client;
}
boolean connected() {
return client != null;
}
void send(Packet<?> packet) {
client.send(packet);
}
static Node fromProperties(String id, Properties properties) { static Node fromProperties(String id, Properties properties) {
return new Node( return new Node(
id, id,
@ -17,4 +51,46 @@ public record Node(
properties.getProperty("host") properties.getProperty("host")
); );
} }
public String id() {
return id;
}
public byte[] key() {
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);
}
@Override
public int hashCode() {
return Objects.hash(id, key, name, host);
}
@Override
public String toString() {
return "Node[" +
"id=" + id + ", " +
"key=" + key + ", " +
"name=" + name + ", " +
"host=" + host + ']';
}
} }

View file

@ -3,8 +3,8 @@ package eu.m724.autopeerer.server;
import eu.m724.autopeerer.common.packet.Packet; import eu.m724.autopeerer.common.packet.Packet;
import eu.m724.autopeerer.common.packet.Packets; import eu.m724.autopeerer.common.packet.Packets;
import eu.m724.autopeerer.common.packet.c2s.LoginPacket; import eu.m724.autopeerer.common.packet.c2s.LoginPacket;
import eu.m724.autopeerer.common.packet.s2c.PingResponsePacket; import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket;
import eu.m724.autopeerer.common.packet.s2c.SessionResponsePacket; import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket;
import java.nio.BufferUnderflowException; import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -46,10 +46,10 @@ public class PacketHandler {
return; return;
} }
if (p instanceof PingResponsePacket packet) { try {
handlePingResponse(state, packet); state.onPacketReceived(p);
} else if (p instanceof SessionResponsePacket packet) { } catch (Exception e) {
handleSessionResponse(state, packet); throw new RuntimeException("Exception handling packet by ClientState", e);
} }
} }
@ -73,6 +73,7 @@ public class PacketHandler {
System.out.printf("[%d] Logged in as %s\n", state.clientId, node.getKey().id()); System.out.printf("[%d] Logged in as %s\n", state.clientId, node.getKey().id());
nodes.put(node.getKey(), state); nodes.put(node.getKey(), state);
node.getKey().setClient(state);
state.authenticated = true; state.authenticated = true;
state.node = node.getKey(); state.node = node.getKey();
} else { } else {
@ -80,12 +81,4 @@ public class PacketHandler {
state.end(); state.end();
} }
} }
private void handlePingResponse(ClientState state, PingResponsePacket packet) {
System.out.printf("[%d] Ping response #%d: %s avg %.3f / mdev %.3f ms\n", state.clientId, packet.requestId, packet.status, packet.average, packet.meanDeviation);
}
private void handleSessionResponse(ClientState state, SessionResponsePacket packet) {
System.out.printf("[%d] Session response #%d: %s\n", state.clientId, packet.sessionId, packet.result);
}
} }

View file

@ -1,2 +1,6 @@
listen.address=127.0.0.1 socket.address=127.0.0.1
listen.port=8002 socket.port=8002
http.address=127.0.0.1
http.port=8003
http.key=very_secret_key