parent
7bd35aab87
commit
1b3da520e7
14 changed files with 176 additions and 47 deletions
|
@ -3,3 +3,11 @@
|
|||
2. Run the JAR, then stop it
|
||||
3. Configure in `config`
|
||||
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
|
5
pom.xml
5
pom.xml
|
@ -20,11 +20,6 @@
|
|||
<artifactId>Java-WebSocket</artifactId>
|
||||
<version>1.5.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.seancfoley</groupId>
|
||||
<artifactId>ipaddress</artifactId>
|
||||
<version>5.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
|
|
45
src/main/java/eu/m724/autopeerer/client/ClientPrefs.java
Normal file
45
src/main/java/eu/m724/autopeerer/client/ClientPrefs.java
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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")));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
50
src/main/java/eu/m724/autopeerer/common/AddressTools.java
Normal file
50
src/main/java/eu/m724/autopeerer/common/AddressTools.java
Normal file
|
@ -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 {}
|
||||
}
|
|
@ -23,7 +23,7 @@ public class SessionResponsePacket implements Packet<SessionResponsePacket> {
|
|||
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<SessionResponsePacket> {
|
|||
}
|
||||
|
||||
public enum SessionResult {
|
||||
OK, ERROR, DUPLICATE
|
||||
OK, ERROR_DUPLICATE, ERROR_RESOLVE, ERROR_OTHER
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SessionRequestPacket> {
|
||||
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<SessionRequestPacket> {
|
|||
|
||||
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<SessionRequestPacket> {
|
|||
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
|
||||
|
|
|
@ -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<SessionResponsePacket> session(long asn, IPv6Address linkLocal, String publicKey, String endpointHost, int endpointPort) {
|
||||
CompletableFuture<SessionResponsePacket> session(long asn, InetAddress linkLocal, String publicKey, String endpointHost, int endpointPort) {
|
||||
var future = new CompletableFuture<SessionResponsePacket>();
|
||||
|
||||
sessionConsumers.put(asn, future::complete);
|
||||
|
@ -66,9 +63,8 @@ public class ClientState {
|
|||
|
||||
/* Packet functions */
|
||||
|
||||
private Map<Short, Consumer<PingResponsePacket>> pingConsumers = new HashMap<>();
|
||||
private Map<Long, Consumer<SessionResponsePacket>> sessionConsumers = new HashMap<>();
|
||||
|
||||
private final Map<Short, Consumer<PingResponsePacket>> pingConsumers = new HashMap<>();
|
||||
private final Map<Long, Consumer<SessionResponsePacket>> sessionConsumers = new HashMap<>();
|
||||
|
||||
void onPacketReceived(Packet<?> p) {
|
||||
if (p instanceof PingResponsePacket packet) {
|
||||
|
|
|
@ -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<Node> 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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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<Node> loadNodes() throws IOException {
|
||||
var nd = new File(directory, "nodes");
|
||||
|
||||
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<Node>();
|
||||
|
||||
|
|
|
@ -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 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
|
3
src/main/resources/nodes/node1.properties
Normal file
3
src/main/resources/nodes/node1.properties
Normal file
|
@ -0,0 +1,3 @@
|
|||
key=AAAAAAAAAAA=
|
||||
name="Node Number One"
|
||||
host=example.com
|
Loading…
Reference in a new issue