diff --git a/pom.xml b/pom.xml
index 5674b5b..f1cb29a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,19 @@
16
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
@@ -50,6 +63,15 @@
1.16.4-R0.1-SNAPSHOT
provided
+
+
+ org.apache.sshd
+ sshd-core
+ 2.14.0
+ compile
+
diff --git a/src/main/java/fi/flexplex/connect/FlexConnect.java b/src/main/java/fi/flexplex/connect/FlexConnect.java
index 196eb6a..077febe 100644
--- a/src/main/java/fi/flexplex/connect/FlexConnect.java
+++ b/src/main/java/fi/flexplex/connect/FlexConnect.java
@@ -3,6 +3,7 @@ package fi.flexplex.connect;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
@@ -11,9 +12,12 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import fi.flexplex.connect.util.FileChangeListener;
+import fi.flexplex.connect.util.Tunnel;
+import fi.flexplex.connect.util.TunnelConnector;
public final class FlexConnect extends JavaPlugin {
+ private final Set tunnelConnectors = new HashSet<>();
private final Set proxyAddresses = new HashSet<>();
private String token = "";
@@ -42,7 +46,11 @@ public final class FlexConnect extends JavaPlugin {
}
// GraphQL Api
- this.flexPlexGraphQLApi = new FlexPlexGraphQLApi(this, "https://api.flexplex.fi/graphql");
+ try {
+ this.flexPlexGraphQLApi = new FlexPlexGraphQLApi(this, "https://api.flexplex.fi/graphql");
+ } catch (final MalformedURLException e) {
+ e.printStackTrace();
+ }
boolean configsModified = false;
@@ -95,12 +103,25 @@ public final class FlexConnect extends JavaPlugin {
// Update whitelists for flexplex api
this.flexPlexGraphQLApi.updateWhitelist();
- // Load real FlexPlex proxy addresses from FlexPlex API
- this.proxyAddresses.addAll(this.flexPlexGraphQLApi.getProxyAddresses());
-
// Load allowed proxy addresses from config
this.proxyAddresses.addAll(this.getConfig().getStringList("allowedProxies"));
+ // Load tunnels and proxy addresses from FlexPlex API and start tunnels
+ final Set tunnels = new HashSet<>();
+ tunnels.addAll(this.flexPlexGraphQLApi.getTunnels());
+ for (final Tunnel tunnel : tunnels) {
+ this.proxyAddresses.add(tunnel.host);
+ final TunnelConnector connector = new TunnelConnector(this, tunnel);
+ this.tunnelConnectors.add(connector);
+ connector.start();
+ }
+ }
+
+ @Override
+ public void onDisable() {
+ for (final TunnelConnector connector : this.tunnelConnectors) {
+ connector.stop();
+ }
}
}
diff --git a/src/main/java/fi/flexplex/connect/FlexConnectListener.java b/src/main/java/fi/flexplex/connect/FlexConnectListener.java
index 597c099..3307449 100644
--- a/src/main/java/fi/flexplex/connect/FlexConnectListener.java
+++ b/src/main/java/fi/flexplex/connect/FlexConnectListener.java
@@ -20,13 +20,15 @@ public final class FlexConnectListener implements Listener {
@EventHandler
public void onPlayerLogin(final PlayerLoginEvent event) {
- if (flexConnect.getProxyAddresses().contains(event.getRealAddress().getHostAddress())) {
+ final String ip = event.getRealAddress().getHostAddress();
+ if (flexConnect.getProxyAddresses().contains(ip)) {
// Connection is coming from allowed proxy
return;
}
// Do not allow connections from other proxies
event.disallow(PlayerLoginEvent.Result.KICK_OTHER, "Access denied");
+ flexConnect.getLogger().warning("Connecting blocked from " + ip);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
diff --git a/src/main/java/fi/flexplex/connect/FlexPlexGraphQLApi.java b/src/main/java/fi/flexplex/connect/FlexPlexGraphQLApi.java
index bff78b6..1b4beaf 100644
--- a/src/main/java/fi/flexplex/connect/FlexPlexGraphQLApi.java
+++ b/src/main/java/fi/flexplex/connect/FlexPlexGraphQLApi.java
@@ -4,6 +4,8 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
@@ -14,23 +16,24 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
+import fi.flexplex.connect.util.Tunnel;
+
import org.bukkit.OfflinePlayer;
public final class FlexPlexGraphQLApi {
- private final String graphqlEndpoint;
+ private final URL graphqlEndpoint;
private final FlexConnect flexConnect;
- public FlexPlexGraphQLApi(final FlexConnect flexConnect, final String graphQLEndpoint) {
+ public FlexPlexGraphQLApi(final FlexConnect flexConnect, final String graphQLEndpoint) throws MalformedURLException {
this.flexConnect = flexConnect;
- this.graphqlEndpoint = graphQLEndpoint;
+ this.graphqlEndpoint = URI.create(graphQLEndpoint).toURL();
}
- public Set getProxyAddresses() {
- final Set output = new HashSet<>();
+ public Set getTunnels() {
+ final Set output = new HashSet<>();
try {
- final URL url = new URL(graphqlEndpoint);
- final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ final HttpURLConnection conn = (HttpURLConnection) graphqlEndpoint.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(10000);
@@ -39,23 +42,28 @@ public final class FlexPlexGraphQLApi {
conn.setRequestProperty("Content-Type", "application/json");
final DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
-
- wr.writeBytes("{\"query\":\"query { proxyAddresses }\"}");
+
+ wr.writeBytes("{\"query\":\"query{tunnels(token:\\\"" + this.flexConnect.getToken() + "\\\"){host,port,forwardingPort}}\"}");
wr.flush();
wr.close();
final JsonObject response = (JsonObject) new JsonParser().parse(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
- if (response.get("data").getAsJsonObject().has("proxyAddresses")) {
- final JsonArray array = response.get("data").getAsJsonObject().get("proxyAddresses").getAsJsonArray();
+ if (response.get("data").getAsJsonObject().has("tunnels")) {
+ final JsonArray array = response.get("data").getAsJsonObject().get("tunnels").getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
- output.add(array.get(i).getAsString());
+ final JsonObject tunnel = array.get(i).getAsJsonObject();
+ output.add(new Tunnel(
+ tunnel.get("host").getAsString(),
+ tunnel.get("port").getAsInt(),
+ tunnel.get("forwardingPort").getAsInt()
+ ));
}
}
conn.disconnect();
} catch (final IOException e) {
- this.flexConnect.getLogger().warning("Updating whitelist data failed, propably connection to FlexPlex API server is down.");
+ this.flexConnect.getLogger().warning("Getting tunnels failed, propably connection to FlexPlex API server is down.");
}
return output;
}
@@ -82,8 +90,7 @@ public final class FlexPlexGraphQLApi {
}
try {
- final URL url = new URL(graphqlEndpoint);
- final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ final HttpURLConnection conn = (HttpURLConnection) graphqlEndpoint.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(10000);
diff --git a/src/main/java/fi/flexplex/connect/util/Tunnel.java b/src/main/java/fi/flexplex/connect/util/Tunnel.java
new file mode 100644
index 0000000..4c91b81
--- /dev/null
+++ b/src/main/java/fi/flexplex/connect/util/Tunnel.java
@@ -0,0 +1,15 @@
+package fi.flexplex.connect.util;
+
+public final class Tunnel {
+
+ public final String host;
+ public final int port;
+ public final int forwardingPort;
+
+ public Tunnel(final String host, final int port, final int forwardingPort) {
+ this.host = host;
+ this.port = port;
+ this.forwardingPort = forwardingPort;
+ }
+
+}
diff --git a/src/main/java/fi/flexplex/connect/util/TunnelConnector.java b/src/main/java/fi/flexplex/connect/util/TunnelConnector.java
new file mode 100644
index 0000000..fd6eccb
--- /dev/null
+++ b/src/main/java/fi/flexplex/connect/util/TunnelConnector.java
@@ -0,0 +1,83 @@
+package fi.flexplex.connect.util;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.forward.PortForwardingTracker;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.bukkit.Bukkit;
+
+import fi.flexplex.connect.FlexConnect;
+
+public final class TunnelConnector {
+
+ private final FlexConnect flexConnect;
+ private final Tunnel tunnel;
+ private final Thread thread;
+ private final SshClient client = SshClient.setUpDefaultClient();
+ private boolean isRunning = false;
+
+ public TunnelConnector(final FlexConnect flexConnect, final Tunnel tunnel) {
+ this.flexConnect = flexConnect;
+ this.tunnel = tunnel;
+ this.client.setForwardingFilter(new TunnelForwardingFilter());
+ this.thread = new Thread() {
+ @Override
+ public void run() {
+ while (isRunning) {
+ session();
+ flexConnect.getLogger().warning("Tunnel session ended. Trying to reconnect in 10 seconds");
+ try {
+ Thread.sleep(10_000);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ };
+ }
+
+ public void start() {
+ this.isRunning = true;
+ this.thread.start();
+ }
+
+ public void stop() {
+ this.isRunning = false;
+ this.client.stop();
+ }
+
+ private void session() {
+ this.flexConnect.getLogger().info("Tunnel connecting to " + this.tunnel.host + ":" + this.tunnel.port);
+ try {
+ client.start();
+
+ final ConnectFuture cf = client.connect("FlexConnect", this.tunnel.host, this.tunnel.port);
+ final ClientSession session = cf.verify().getSession();
+
+ session.addPasswordIdentity(flexConnect.getToken());
+ session.auth().verify(TimeUnit.SECONDS.toMillis(5));
+
+ final SshdSocketAddress remote = new SshdSocketAddress("127.0.0.1", this.tunnel.forwardingPort);
+ final SshdSocketAddress local = new SshdSocketAddress(Bukkit.getPort());
+
+ try (PortForwardingTracker tracker = session.createRemotePortForwardingTracker(remote, local)) {
+ this.flexConnect.getLogger().info("Tunnel successfully connected " + this.tunnel.host + ":" + this.tunnel.port);
+ while (isRunning) {
+ Thread.sleep(5000);
+ if (session.isClosed()) {
+ break;
+ }
+ }
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ } catch (final IOException e) {
+ this.flexConnect.getLogger().warning(e.getMessage());
+ }
+ }
+
+}
diff --git a/src/main/java/fi/flexplex/connect/util/TunnelForwardingFilter.java b/src/main/java/fi/flexplex/connect/util/TunnelForwardingFilter.java
new file mode 100644
index 0000000..7194b0a
--- /dev/null
+++ b/src/main/java/fi/flexplex/connect/util/TunnelForwardingFilter.java
@@ -0,0 +1,29 @@
+package fi.flexplex.connect.util;
+
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.server.forward.ForwardingFilter;
+
+public final class TunnelForwardingFilter implements ForwardingFilter {
+
+ @Override
+ public boolean canForwardAgent(final Session session, final String requestType) {
+ return false;
+ }
+
+ @Override
+ public boolean canForwardX11(final Session session, final String requestType) {
+ return false;
+ }
+
+ @Override
+ public boolean canListen(final SshdSocketAddress address, final Session session) {
+ return false;
+ }
+
+ @Override
+ public boolean canConnect(final Type type, final SshdSocketAddress address, final Session session) {
+ return true;
+ }
+
+}