diff --git a/README.md b/README.md
index b5d1886..626a39d 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,12 @@
1. Get the `-server.jar` or `-client.jar` and make a new directory for it
2. Run the JAR, then stop it
3. Configure in `config`
-4. Now run the JAR. Done!
\ No newline at end of file
+4. Now run the JAR. Done!
+
+# Client config
+- `remote` the autopeerer server websocket URL
+- `wireguard.directory` directory for WireGuard config files
+- `bird.directory` directory for BIRD config files
+- `link-local` the link local for peering
+- `stack.ipv4` IPv4 availability. Values: `no`, `yes`, `restricted`
+- `stack.ipv6` same but for IPv6
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index db1d5c3..fef70cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,11 +20,6 @@
Java-WebSocket
1.5.7
-
- com.github.seancfoley
- ipaddress
- 5.5.1
-
org.json
json
diff --git a/src/main/java/eu/m724/autopeerer/client/ClientPrefs.java b/src/main/java/eu/m724/autopeerer/client/ClientPrefs.java
new file mode 100644
index 0000000..3072aef
--- /dev/null
+++ b/src/main/java/eu/m724/autopeerer/client/ClientPrefs.java
@@ -0,0 +1,45 @@
+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;
+ }
+
+}
diff --git a/src/main/java/eu/m724/autopeerer/client/Main.java b/src/main/java/eu/m724/autopeerer/client/Main.java
index 772f439..ab2cfa9 100644
--- a/src/main/java/eu/m724/autopeerer/client/Main.java
+++ b/src/main/java/eu/m724/autopeerer/client/Main.java
@@ -23,6 +23,8 @@ public class Main {
return;
}
+ ClientPrefs.init(config);
+
URI serverUri = URI.create(config.getString("remote"));
var wireGuardLive = new WireGuardLive(new File(config.getString("wireguard.directory")));
diff --git a/src/main/java/eu/m724/autopeerer/client/PacketHandler.java b/src/main/java/eu/m724/autopeerer/client/PacketHandler.java
index a5080ad..28632f9 100644
--- a/src/main/java/eu/m724/autopeerer/client/PacketHandler.java
+++ b/src/main/java/eu/m724/autopeerer/client/PacketHandler.java
@@ -5,6 +5,7 @@ import eu.m724.autopeerer.client.bird.BirdSession;
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.PingRequestPacket;
@@ -14,6 +15,7 @@ import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket;
import java.io.BufferedReader;
import java.io.IOException;
+import java.net.UnknownHostException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.file.FileAlreadyExistsException;
@@ -96,6 +98,24 @@ public class PacketHandler {
}
private void handleSessionRequest(SessionRequestPacket packet) {
+ // validate endpoint
+ var resolved = false;
+ try {
+ if (ClientPrefs.ipv6Supported()) {
+ var res = AddressTools.resolve(packet.endpointHost, true);
+ resolved = res != null;
+ }
+ if (!resolved && ClientPrefs.ipv4Supported()) {
+ 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);
+ return;
+ }
+
var privateKey = WireGuardKeys.generatePrivateKey();
var publicKey = WireGuardKeys.derivePublicKey(privateKey);
@@ -104,8 +124,8 @@ public class PacketHandler {
port = (int) (packet.asn % 10000);
}
- var wireGuardSession = new WireGuardSession(port, privateKey, serverLinkLocal, packet.linkLocal.toCompressedString(), packet.endpointHost + ":" + packet.endpointPort, packet.publicKey);
- var birdSession = new BirdSession(packet.asn, packet.linkLocal.toCompressedString());
+ 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());
try {
wireGuardLive.saveSession(packet.asn, wireGuardSession);
@@ -115,9 +135,10 @@ public class PacketHandler {
Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.OK, wireGuardSession.listenPort(), publicKey), sender);
} 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.DUPLICATE, -1, null), sender);
+ Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR_DUPLICATE, -1, null), sender);
} catch (IOException e) {
- Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR, -1, null), sender);
+ Packets.send(new SessionResponsePacket(packet.asn, SessionResponsePacket.SessionResult.ERROR_OTHER, -1, null), sender);
+ System.err.println("Failed to save session");
throw new RuntimeException(e);
}
}
diff --git a/src/main/java/eu/m724/autopeerer/common/AddressTools.java b/src/main/java/eu/m724/autopeerer/common/AddressTools.java
new file mode 100644
index 0000000..e86a89e
--- /dev/null
+++ b/src/main/java/eu/m724/autopeerer/common/AddressTools.java
@@ -0,0 +1,50 @@
+package eu.m724.autopeerer.common;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+public class AddressTools {
+ private static boolean isIp(InetAddress address, boolean ipv6) {
+ return ipv6 ? address instanceof Inet6Address : address instanceof Inet4Address;
+ }
+
+ /**
+ * Checks if address is link local
+ * @param address the address
+ * @return whether it's IPv6 and link local
+ */
+ public static boolean isLinkLocal(InetAddress address) {
+ if (isIp(address, true)) return false;
+ var bytes = address.getAddress();
+
+ return bytes[0] == -2 && bytes[1] >= 0 && bytes[1] <= 63;
+ }
+
+ /**
+ * Resolve a host to IPv4 or IPv6
+ *
+ * @param host the host to resolve
+ * @param ipv6 whether to resolve IPv6 or not (IPv4)
+ * @return The resolved IP or null
+ * @throws UnknownHostException if host is invalid
+ * @throws MultipleRecordsException if host has multiple records
+ */
+ public static InetAddress resolve(String host, boolean ipv6) throws UnknownHostException, MultipleRecordsException {
+ var addr = Arrays.stream(InetAddress.getAllByName(host))
+ .filter(address -> isIp(address, ipv6))
+ .toList();
+
+ if (addr.size() > 1)
+ throw new MultipleRecordsException();
+
+ return addr.get(0);
+ }
+
+ /**
+ * If a host has multiple A or AAAA records, and it's impossible to pick one
+ */
+ public static class MultipleRecordsException extends Exception {}
+}
diff --git a/src/main/java/eu/m724/autopeerer/common/packet/c2s/SessionResponsePacket.java b/src/main/java/eu/m724/autopeerer/common/packet/c2s/SessionResponsePacket.java
index 22146e6..ec93359 100644
--- a/src/main/java/eu/m724/autopeerer/common/packet/c2s/SessionResponsePacket.java
+++ b/src/main/java/eu/m724/autopeerer/common/packet/c2s/SessionResponsePacket.java
@@ -23,7 +23,7 @@ public class SessionResponsePacket implements Packet {
return 2;
}
- public static SessionResponsePacket deserialize(ByteBuffer buffer) throws Exception {
+ public static SessionResponsePacket deserialize(ByteBuffer buffer) {
var asn = Integer.toUnsignedLong(buffer.getInt());
var result = SessionResult.values()[buffer.get()];
@@ -57,6 +57,6 @@ public class SessionResponsePacket implements Packet {
}
public enum SessionResult {
- OK, ERROR, DUPLICATE
+ OK, ERROR_DUPLICATE, ERROR_RESOLVE, ERROR_OTHER
}
}
diff --git a/src/main/java/eu/m724/autopeerer/common/packet/s2c/SessionRequestPacket.java b/src/main/java/eu/m724/autopeerer/common/packet/s2c/SessionRequestPacket.java
index 361965f..90f6489 100644
--- a/src/main/java/eu/m724/autopeerer/common/packet/s2c/SessionRequestPacket.java
+++ b/src/main/java/eu/m724/autopeerer/common/packet/s2c/SessionRequestPacket.java
@@ -1,39 +1,33 @@
package eu.m724.autopeerer.common.packet.s2c;
+import eu.m724.autopeerer.common.AddressTools;
import eu.m724.autopeerer.common.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.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class SessionRequestPacket implements Packet {
public final long asn;
- public final IPv6Address linkLocal;
+ public final InetAddress linkLocal;
public final String publicKey;
public final String endpointHost;
public final int endpointPort;
- public SessionRequestPacket(long asn, IPv6Address linkLocal, String publicKey, String endpointHost, int endpointPort) {
+ public SessionRequestPacket(long asn, InetAddress linkLocal, String publicKey, String endpointHost, int endpointPort) {
this.asn = asn;
assert asn < 0xFFFFFFFFL;
this.linkLocal = linkLocal;
- assert new IPAddressString("fe80::/10").getAddress().contains(linkLocal);
+ assert AddressTools.isLinkLocal(linkLocal);
this.publicKey = publicKey;
assert Base64.getDecoder().decode(publicKey).length == 32;
+ // testing is done at another point
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;
@@ -49,7 +43,7 @@ public class SessionRequestPacket implements Packet {
var ll = new byte[16];
buffer.get(ll);
- var linkLocal = new IPv6Address(ll);
+ var linkLocal = InetAddress.getByAddress(ll);
var pk = new byte[32];
buffer.get(pk);
@@ -70,7 +64,7 @@ public class SessionRequestPacket implements Packet {
var buffer = ByteBuffer.allocate(55 + endpointHost.length());
buffer.putInt((int) asn);
- buffer.put(linkLocal.getBytes()); // 16b
+ buffer.put(linkLocal.getAddress()); // 16b
buffer.put(Base64.getDecoder().decode(publicKey)); // 32b
buffer.putShort((short) endpointPort); // 2b
buffer.put((byte) endpointHost.length()); // 1b
diff --git a/src/main/java/eu/m724/autopeerer/server/ClientState.java b/src/main/java/eu/m724/autopeerer/server/ClientState.java
index f04fcaf..3b0647e 100644
--- a/src/main/java/eu/m724/autopeerer/server/ClientState.java
+++ b/src/main/java/eu/m724/autopeerer/server/ClientState.java
@@ -6,15 +6,12 @@ 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 inet.ipaddr.ipv6.IPv6Address;
import org.java_websocket.WebSocket;
import java.net.InetAddress;
-import java.net.UnknownHostException;
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;
@@ -52,7 +49,7 @@ public class ClientState {
return future;
}
- CompletableFuture session(long asn, IPv6Address linkLocal, String publicKey, String endpointHost, int endpointPort) {
+ CompletableFuture session(long asn, InetAddress linkLocal, String publicKey, String endpointHost, int endpointPort) {
var future = new CompletableFuture();
sessionConsumers.put(asn, future::complete);
@@ -66,9 +63,8 @@ public class ClientState {
/* Packet functions */
- private Map> pingConsumers = new HashMap<>();
- private Map> sessionConsumers = new HashMap<>();
-
+ private final Map> pingConsumers = new HashMap<>();
+ private final Map> sessionConsumers = new HashMap<>();
void onPacketReceived(Packet> p) {
if (p instanceof PingResponsePacket packet) {
diff --git a/src/main/java/eu/m724/autopeerer/server/Main.java b/src/main/java/eu/m724/autopeerer/server/Main.java
index 9c28776..dae011b 100644
--- a/src/main/java/eu/m724/autopeerer/server/Main.java
+++ b/src/main/java/eu/m724/autopeerer/server/Main.java
@@ -8,17 +8,11 @@ import java.util.Set;
public class Main {
public static void main(String[] args) throws IOException {
- System.out.println("Hello world!");
-
var config = new ServerConfiguration();
Set nodes;
try {
config.load();
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 load configuration", e);
}
@@ -28,7 +22,15 @@ public class Main {
return;
}
+ 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());
+
+
+
var packetHandler = new PacketHandler(nodes);
+
var server = new MyWebsocketServer(
new InetSocketAddress(config.getString("socket.address"), config.getInt("socket.port")),
packetHandler
diff --git a/src/main/java/eu/m724/autopeerer/server/MyHttpHandler.java b/src/main/java/eu/m724/autopeerer/server/MyHttpHandler.java
index 86b738d..bda1e91 100644
--- a/src/main/java/eu/m724/autopeerer/server/MyHttpHandler.java
+++ b/src/main/java/eu/m724/autopeerer/server/MyHttpHandler.java
@@ -4,9 +4,6 @@ import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import eu.m724.autopeerer.common.packet.c2s.PingResponsePacket;
import eu.m724.autopeerer.common.packet.c2s.SessionResponsePacket;
-import inet.ipaddr.IPAddress;
-import inet.ipaddr.IPAddressString;
-import inet.ipaddr.ipv6.IPv6Address;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -43,7 +40,7 @@ public class MyHttpHandler implements HttpHandler {
inner(exchange);
} catch (IOException e) {
exchange.sendResponseHeaders(500, -1);
- } catch (EndException e) { }
+ } catch (EndException ignored) { }
}
private void inner(HttpExchange exchange) throws IOException, EndException {
@@ -116,7 +113,7 @@ public class MyHttpHandler implements HttpHandler {
sseWrite(exchange, response.toString());
} catch (IOException e) {
e.printStackTrace();
- } catch (EndException e) { }
+ } catch (EndException ignored) { }
return (Void) null;
});
@@ -165,7 +162,7 @@ public class MyHttpHandler implements HttpHandler {
try {
future = node.getClient().session(
json.getLong("asn"),
- new IPAddressString(json.getString("linkLocal")).getAddress().toIPv6(),
+ InetAddress.getByName(json.getString("linkLocal")),
json.getString("publicKey"),
json.getString("endpointHost"),
json.getInt("endpointPort")
diff --git a/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java b/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java
index 144e47f..c05a563 100644
--- a/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java
+++ b/src/main/java/eu/m724/autopeerer/server/ServerConfiguration.java
@@ -6,6 +6,7 @@ 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;
@@ -17,7 +18,14 @@ public class ServerConfiguration extends Configuration {
public Set loadNodes() throws IOException {
var nd = new File(directory, "nodes");
- nd.mkdir();
+
+ 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());
+ }
+ }
var nodes = new HashSet();
diff --git a/src/main/resources/client.properties b/src/main/resources/client.properties
index 45dd800..1a7d92b 100644
--- a/src/main/resources/client.properties
+++ b/src/main/resources/client.properties
@@ -1,4 +1,12 @@
+# The server websocket
remote=ws://127.0.0.1:8002
+# Where are WG and BIRD configs located
wireguard.directory=config/wg
bird.directory=config/bird
-link-local=fe80::129:0
\ No newline at end of file
+
+# 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
\ No newline at end of file
diff --git a/src/main/resources/nodes/node1.properties b/src/main/resources/nodes/node1.properties
new file mode 100644
index 0000000..6ec89e4
--- /dev/null
+++ b/src/main/resources/nodes/node1.properties
@@ -0,0 +1,3 @@
+key=AAAAAAAAAAA=
+name="Node Number One"
+host=example.com
\ No newline at end of file