parent
f8903d841c
commit
1437c720c0
14 changed files with 263 additions and 15 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -35,4 +35,7 @@ build/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# autopeerer
|
||||||
|
/config/
|
||||||
|
|
3
config/nodes/node1.properties
Normal file
3
config/nodes/node1.properties
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
key=AAAAAAAAAAA=
|
||||||
|
name="Node Number One"
|
||||||
|
host=example.com
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.m724.autopeerer.client;
|
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.client.WebSocketClient;
|
||||||
import org.java_websocket.handshake.ServerHandshake;
|
import org.java_websocket.handshake.ServerHandshake;
|
||||||
|
|
||||||
|
@ -26,6 +28,8 @@ public class MyWebsocketClient extends WebSocketClient {
|
||||||
public void onOpen(ServerHandshake serverHandshake) {
|
public void onOpen(ServerHandshake serverHandshake) {
|
||||||
double connectTime = (System.nanoTime() - connectStart) / 1000000.0;
|
double connectTime = (System.nanoTime() - connectStart) / 1000000.0;
|
||||||
System.out.printf("Connected in %.3f ms\n", connectTime);
|
System.out.printf("Connected in %.3f ms\n", connectTime);
|
||||||
|
|
||||||
|
Packets.send(new LoginPacket(new byte[8]), this::send);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -55,16 +55,14 @@ public class PacketHandler {
|
||||||
var status = PingResponsePacket.PingResponseStatus.ERROR;
|
var status = PingResponsePacket.PingResponseStatus.ERROR;
|
||||||
|
|
||||||
try {
|
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()) {
|
try (BufferedReader reader = process.inputReader()) {
|
||||||
for (String l : reader.lines().toList()) {
|
for (String l : reader.lines().toList()) {
|
||||||
boolean error = l.startsWith("From"); // indicates an error
|
|
||||||
boolean end = l.startsWith("rtt");
|
boolean end = l.startsWith("rtt");
|
||||||
|
|
||||||
if (error) {
|
if (l.startsWith("PING")) {
|
||||||
status = PingResponsePacket.PingResponseStatus.UNREACHABLE;
|
status = PingResponsePacket.PingResponseStatus.UNREACHABLE;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end) {
|
if (end) {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package eu.m724.autopeerer.common.packet;
|
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.PingRequestPacket;
|
||||||
import eu.m724.autopeerer.common.packet.client.SessionRequestPacket;
|
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.PingResponsePacket;
|
||||||
import eu.m724.autopeerer.common.packet.server.SessionResponsePacket;
|
import eu.m724.autopeerer.common.packet.server.SessionResponsePacket;
|
||||||
|
|
||||||
|
@ -14,7 +16,9 @@ public class Packets {
|
||||||
|
|
||||||
Packet<?> packet = null;
|
Packet<?> packet = null;
|
||||||
|
|
||||||
if (id == 1) {
|
if (id == 0) {
|
||||||
|
packet = LoginResponsePacket.deserialize(buffer);
|
||||||
|
} else if (id == 1) {
|
||||||
packet = PingRequestPacket.deserialize(buffer);
|
packet = PingRequestPacket.deserialize(buffer);
|
||||||
} else if (id == 2) {
|
} else if (id == 2) {
|
||||||
packet = SessionRequestPacket.deserialize(buffer);
|
packet = SessionRequestPacket.deserialize(buffer);
|
||||||
|
@ -28,7 +32,9 @@ public class Packets {
|
||||||
|
|
||||||
Packet<?> packet = null;
|
Packet<?> packet = null;
|
||||||
|
|
||||||
if (id == 1) {
|
if (id == 0) {
|
||||||
|
packet = LoginPacket.deserialize(buffer);
|
||||||
|
} else if (id == 1) {
|
||||||
packet = PingResponsePacket.deserialize(buffer);
|
packet = PingResponsePacket.deserialize(buffer);
|
||||||
} else if (id == 2) {
|
} else if (id == 2) {
|
||||||
packet = SessionResponsePacket.deserialize(buffer);
|
packet = SessionResponsePacket.deserialize(buffer);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,8 +8,10 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class ClientState {
|
public class ClientState {
|
||||||
|
public boolean authenticated = false;
|
||||||
public final int clientId;
|
public final int clientId;
|
||||||
public final WebSocket socket;
|
public final WebSocket socket;
|
||||||
|
Node node;
|
||||||
|
|
||||||
private final Consumer<ByteBuffer> sender;
|
private final Consumer<ByteBuffer> sender;
|
||||||
|
|
||||||
|
@ -22,4 +24,8 @@ public class ClientState {
|
||||||
void send(Packet<?> packet) {
|
void send(Packet<?> packet) {
|
||||||
Packets.send(packet, sender);
|
Packets.send(packet, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void end() {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,32 @@
|
||||||
package eu.m724.autopeerer.server;
|
package eu.m724.autopeerer.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("Hello world!");
|
System.out.println("Hello world!");
|
||||||
|
|
||||||
var packetHandler = new PacketHandler();
|
var config = new ServerConfiguration();
|
||||||
var client = new MyWebsocketServer(packetHandler);
|
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();
|
client.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,15 @@ import java.util.Map;
|
||||||
|
|
||||||
public class MyWebsocketServer extends WebSocketServer {
|
public class MyWebsocketServer extends WebSocketServer {
|
||||||
private final PacketHandler packetHandler;
|
private final PacketHandler packetHandler;
|
||||||
|
|
||||||
private final Map<WebSocket, ClientState> states = new HashMap<>();
|
private final Map<WebSocket, ClientState> states = new HashMap<>();
|
||||||
|
|
||||||
private int id = 0;
|
private int id = 0;
|
||||||
|
|
||||||
public MyWebsocketServer(PacketHandler packetHandler) {
|
public MyWebsocketServer(InetSocketAddress address, PacketHandler packetHandler) {
|
||||||
super(new InetSocketAddress("127.0.0.1", 8002));
|
super(address);
|
||||||
this.setReuseAddr(true);
|
this.setReuseAddr(true);
|
||||||
|
|
||||||
this.packetHandler = packetHandler;
|
this.packetHandler = packetHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +37,8 @@ public class MyWebsocketServer extends WebSocketServer {
|
||||||
// TODO testing
|
// TODO testing
|
||||||
try {
|
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.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(
|
state.send(new SessionRequestPacket(
|
||||||
(short) 1,
|
(short) 1,
|
||||||
|
@ -52,7 +55,8 @@ public class MyWebsocketServer extends WebSocketServer {
|
||||||
@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);
|
||||||
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
|
@Override
|
||||||
|
|
20
src/main/java/eu/m724/autopeerer/server/Node.java
Normal file
20
src/main/java/eu/m724/autopeerer/server/Node.java
Normal 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")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,14 +2,21 @@ 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.client.LoginPacket;
|
||||||
import eu.m724.autopeerer.common.packet.server.PingResponsePacket;
|
import eu.m724.autopeerer.common.packet.server.PingResponsePacket;
|
||||||
import eu.m724.autopeerer.common.packet.server.SessionResponsePacket;
|
import eu.m724.autopeerer.common.packet.server.SessionResponsePacket;
|
||||||
|
|
||||||
import java.nio.BufferUnderflowException;
|
import java.nio.BufferUnderflowException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Base64;
|
import java.util.*;
|
||||||
|
|
||||||
public class PacketHandler {
|
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) {
|
void handle(ClientState state, ByteBuffer bytes) {
|
||||||
// TODO this is not safe but enough for now
|
// TODO this is not safe but enough for now
|
||||||
Packet<?> p;
|
Packet<?> p;
|
||||||
|
@ -29,6 +36,16 @@ public class PacketHandler {
|
||||||
throw new RuntimeException(e);
|
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) {
|
if (p instanceof PingResponsePacket packet) {
|
||||||
handlePingResponse(state, packet);
|
handlePingResponse(state, packet);
|
||||||
} else if (p instanceof SessionResponsePacket 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) {
|
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);
|
System.out.printf("[%d] Ping response #%d: %s avg %.3f / mdev %.3f ms\n", state.clientId, packet.requestId, packet.status, packet.average, packet.meanDeviation);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
2
src/main/resources/server.properties
Normal file
2
src/main/resources/server.properties
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
listen.address=127.0.0.1
|
||||||
|
listen.port=8002
|
Loading…
Reference in a new issue