TCP
All checks were successful
/ deploy (push) Successful in 28s

This commit is contained in:
Minecon724 2024-12-26 20:54:23 +01:00
parent 5874e8b47e
commit b3752fd4ae
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
27 changed files with 504 additions and 306 deletions

View file

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

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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
}
}

View file

@ -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) { }
}

View file

@ -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;
}
}
}

View file

@ -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");

View file

@ -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);
}
}

View file

@ -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");

View file

@ -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;
}
/**

View file

@ -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
) {
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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
);
}
}

View file

@ -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) {

View file

@ -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
);

View file

@ -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"),

View file

@ -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();
}
}

View file

@ -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) {
}
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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());
}
}

View file

@ -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
# Other stuff is configured on the server

View file

@ -1,3 +1,10 @@
key=AAAAAAAAAAA=
name="Node Number One"
host=example.com
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