A
All checks were successful
/ deploy (push) Successful in 1m21s

This commit is contained in:
Minecon724 2024-12-14 12:33:21 +01:00
parent f8903d841c
commit 1437c720c0
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
14 changed files with 263 additions and 15 deletions

3
.gitignore vendored
View file

@ -36,3 +36,6 @@ build/
### Mac OS ###
.DS_Store
# autopeerer
/config/

View file

@ -0,0 +1,3 @@
key=AAAAAAAAAAA=
name="Node Number One"
host=example.com

View file

@ -1,5 +1,7 @@
package eu.m724.autopeerer.client;
import eu.m724.autopeerer.common.packet.Packets;
import eu.m724.autopeerer.common.packet.client.LoginPacket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
@ -26,6 +28,8 @@ public class MyWebsocketClient extends WebSocketClient {
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

View file

@ -55,16 +55,14 @@ public class PacketHandler {
var status = PingResponsePacket.PingResponseStatus.ERROR;
try {
Process process = Runtime.getRuntime().exec(new String[] { "ping", "-3Ac", "10", packet.target.getHostAddress() });
Process process = Runtime.getRuntime().exec(new String[] { "ping", "-3Ac", "10", "-W", "1", 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) {
if (l.startsWith("PING")) {
status = PingResponsePacket.PingResponseStatus.UNREACHABLE;
break;
}
if (end) {

View file

@ -1,7 +1,9 @@
package eu.m724.autopeerer.common.packet;
import eu.m724.autopeerer.common.packet.client.LoginPacket;
import eu.m724.autopeerer.common.packet.client.PingRequestPacket;
import eu.m724.autopeerer.common.packet.client.SessionRequestPacket;
import eu.m724.autopeerer.common.packet.server.LoginResponsePacket;
import eu.m724.autopeerer.common.packet.server.PingResponsePacket;
import eu.m724.autopeerer.common.packet.server.SessionResponsePacket;
@ -14,7 +16,9 @@ public class Packets {
Packet<?> packet = null;
if (id == 1) {
if (id == 0) {
packet = LoginResponsePacket.deserialize(buffer);
} else if (id == 1) {
packet = PingRequestPacket.deserialize(buffer);
} else if (id == 2) {
packet = SessionRequestPacket.deserialize(buffer);
@ -28,7 +32,9 @@ public class Packets {
Packet<?> packet = null;
if (id == 1) {
if (id == 0) {
packet = LoginPacket.deserialize(buffer);
} else if (id == 1) {
packet = PingResponsePacket.deserialize(buffer);
} else if (id == 2) {
packet = SessionResponsePacket.deserialize(buffer);

View file

@ -0,0 +1,33 @@
package eu.m724.autopeerer.common.packet.client;
import eu.m724.autopeerer.common.packet.Packet;
import java.nio.ByteBuffer;
public class LoginPacket implements Packet<LoginPacket> {
public final byte[] key;
public LoginPacket(byte[] key) {
this.key = key;
assert key.length == 8;
}
@Override
public byte getId() {
return 0;
}
@Override
public ByteBuffer serialize() {
var buffer = ByteBuffer.allocate(8);
buffer.put(key);
return buffer;
}
public static LoginPacket deserialize(ByteBuffer buffer) {
var key = new byte[8];
buffer.get(key);
return new LoginPacket(key);
}
}

View file

@ -0,0 +1,36 @@
package eu.m724.autopeerer.common.packet.server;
import eu.m724.autopeerer.common.packet.Packet;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class LoginResponsePacket implements Packet<LoginResponsePacket> {
public final String nodeId;
public LoginResponsePacket(String nodeId) {
this.nodeId = nodeId;
}
@Override
public byte getId() {
return 0;
}
@Override
public ByteBuffer serialize() {
var nodeIdEncoded = nodeId.getBytes(StandardCharsets.UTF_8);
var buffer = ByteBuffer.allocate(1 + nodeIdEncoded.length);
buffer.put((byte) nodeIdEncoded.length);
buffer.put(nodeIdEncoded);
return buffer;
}
public static LoginResponsePacket deserialize(ByteBuffer buffer) {
var size = Byte.toUnsignedInt(buffer.get());
var nodeIdEncoded = new byte[size];
buffer.get(nodeIdEncoded);
return new LoginResponsePacket(new String(nodeIdEncoded, StandardCharsets.UTF_8));
}
}

View file

@ -8,8 +8,10 @@ import java.nio.ByteBuffer;
import java.util.function.Consumer;
public class ClientState {
public boolean authenticated = false;
public final int clientId;
public final WebSocket socket;
Node node;
private final Consumer<ByteBuffer> sender;
@ -22,4 +24,8 @@ public class ClientState {
void send(Packet<?> packet) {
Packets.send(packet, sender);
}
void end() {
socket.close();
}
}

View file

@ -1,13 +1,32 @@
package eu.m724.autopeerer.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Set;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
var packetHandler = new PacketHandler();
var client = new MyWebsocketServer(packetHandler);
var config = new ServerConfiguration();
Set<Node> nodes;
try {
config.init();
nodes = config.loadNodes();
if (nodes.isEmpty())
System.err.println("Loaded 0 nodes. No client will be able to connect.");
else
System.out.printf("Loaded %d nodes.\n", nodes.size());
} catch (IOException e) {
throw new RuntimeException("Failed to init configuration", e);
}
var packetHandler = new PacketHandler(nodes);
var client = new MyWebsocketServer(
new InetSocketAddress(config.getString("listen.address"), config.getInt("listen.port")),
packetHandler
);
client.setDaemon(false);
client.start();
}
}

View file

@ -16,13 +16,15 @@ 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(PacketHandler packetHandler) {
super(new InetSocketAddress("127.0.0.1", 8002));
public MyWebsocketServer(InetSocketAddress address, PacketHandler packetHandler) {
super(address);
this.setReuseAddr(true);
this.packetHandler = packetHandler;
}
@ -35,7 +37,8 @@ public class MyWebsocketServer extends WebSocketServer {
// TODO testing
try {
state.send(new PingRequestPacket((short) 1, InetAddress.getByName("1.1.1.1")));
state.send(new PingRequestPacket((short) 2, InetAddress.getByName("1.1.1.2")));
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 SessionRequestPacket(
(short) 1,
@ -52,7 +55,8 @@ public class MyWebsocketServer extends WebSocketServer {
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
var state = states.remove(conn);
System.out.printf("[%d] Disconnected: %s\n", state.clientId, conn.getRemoteSocketAddress().getHostString());
var id = state.authenticated ? state.node.id() : conn.getRemoteSocketAddress().getHostString();
System.out.printf("[%d] Disconnected: %s\n", state.clientId, id);
}
@Override

View file

@ -0,0 +1,20 @@
package eu.m724.autopeerer.server;
import java.util.Base64;
import java.util.Properties;
public record Node(
String id,
byte[] key,
String name,
String host
) {
static Node fromProperties(String id, Properties properties) {
return new Node(
id,
Base64.getDecoder().decode(properties.getProperty("key")),
properties.getProperty("name"),
properties.getProperty("host")
);
}
}

View file

@ -2,14 +2,21 @@ package eu.m724.autopeerer.server;
import eu.m724.autopeerer.common.packet.Packet;
import eu.m724.autopeerer.common.packet.Packets;
import eu.m724.autopeerer.common.packet.client.LoginPacket;
import eu.m724.autopeerer.common.packet.server.PingResponsePacket;
import eu.m724.autopeerer.common.packet.server.SessionResponsePacket;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.*;
public class PacketHandler {
private final Map<Node, ClientState> nodes = new HashMap<>();
public PacketHandler(Set<Node> nodes) {
nodes.forEach(n -> this.nodes.put(n, null));
}
void handle(ClientState state, ByteBuffer bytes) {
// TODO this is not safe but enough for now
Packet<?> p;
@ -29,6 +36,16 @@ public class PacketHandler {
throw new RuntimeException(e);
}
if (!state.authenticated) {
if (p instanceof LoginPacket packet) {
handleLogin(state, packet);
} else {
System.out.printf("[%d] Sent %s while unauthenticated\n", state.clientId, p.getClass().getName());
}
return;
}
if (p instanceof PingResponsePacket packet) {
handlePingResponse(state, packet);
} else if (p instanceof SessionResponsePacket packet) {
@ -36,6 +53,34 @@ public class PacketHandler {
}
}
void handleClose(ClientState state) {
nodes.entrySet().stream()
.filter(e -> e.getValue().equals(state))
.findFirst()
.ifPresent(e -> nodes.remove(e.getKey()));
}
private void handleLogin(ClientState state, LoginPacket packet) {
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
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);
state.authenticated = true;
state.node = node.getKey();
} else {
System.out.printf("[%d] Tried to log in with invalid key\n", state.clientId);
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);
}

View file

@ -0,0 +1,69 @@
package eu.m724.autopeerer.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
public class ServerConfiguration {
private final File directory = new File("config");
private final File configFile = new File(directory, "server.properties");
private final Properties properties = new Properties();
public void init() throws IOException {
directory.mkdir();
if (!configFile.exists()) {
try (var is = getClass().getClassLoader().getResourceAsStream("server.properties")) {
Files.write(configFile.toPath(), is.readAllBytes());
properties.load(is);
}
}
try (var is = new FileInputStream(configFile)) {
properties.load(is);
}
}
public Set<Node> loadNodes() throws IOException {
var nd = new File(directory, "nodes");
nd.mkdir();
var nodes = new HashSet<Node>();
for (var f : nd.listFiles()) {
var properties = new Properties();
try (var is = new FileInputStream(f)) {
properties.load(is);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
nodes.add(Node.fromProperties(f.getName().split("\\.")[0], properties));
}
return nodes;
}
public String getString(String property) {
return properties.getProperty(property);
}
public String getString(String property, String def) {
return properties.getProperty(property, def);
}
public int getInt(String property) {
return Integer.parseInt(getString(property));
}
public int getInt(String property, int def) {
return Integer.parseInt(getString(property, String.valueOf(def)));
}
}

View file

@ -0,0 +1,2 @@
listen.address=127.0.0.1
listen.port=8002