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