From 6331e1af3ef9a848cb654ce8c63b782c3a36e10a Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Fri, 23 Oct 2020 18:08:02 +0200 Subject: [PATCH 1/3] Velocity Dump WIP --- .../command/builtin/VelocityCommand.java | 49 ++++++ .../proxy/config/VelocityConfiguration.java | 55 ++++--- .../proxy/util/InformationUtils.java | 154 ++++++++++++++++++ 3 files changed, 231 insertions(+), 27 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 02c085241..52aa7468a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -3,19 +3,27 @@ package com.velocitypowered.proxy.command.builtin; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; + +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonObject; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.util.InformationUtils; + import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; + import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -52,6 +60,7 @@ public class VelocityCommand implements SimpleCommand { .put("version", new Info(server)) .put("plugins", new Plugins(server)) .put("reload", new Reload(server)) + .put("dump", new Dump(server)) .build(); } @@ -289,4 +298,44 @@ public class VelocityCommand implements SimpleCommand { return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE; } } + + private static class Dump implements SubCommand { + + private final ProxyServer server; + + private Dump(ProxyServer server) { + this.server = server; + } + + @Override + public void execute(CommandSource source, String @NonNull [] args) { + if (args.length != 0) { + source.sendMessage(Identity.nil(), Component.text("/velocity dump", NamedTextColor.RED)); + return; + } + + Collection allServers = ImmutableSet.copyOf(server.getAllServers()); + JsonObject servers = new JsonObject(); + for (RegisteredServer iter : allServers) { + servers.add(iter.getServerInfo().getName(), + InformationUtils.collectServerInfo(iter)); + } + + JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.getConfiguration()); + proxyConfig.add("servers", servers); + + JsonObject dump = new JsonObject(); + dump.add("versionInfo", InformationUtils.collectProxyInfo(server.getVersion())); + dump.add("platform", InformationUtils.collectEnvironmentInfo()); + dump.add("config", proxyConfig); + dump.add("plugins", InformationUtils.collectPluginInfo(server)); + + // TODO: Finish + } + + @Override + public boolean hasPermission(final CommandSource source, final String @NonNull [] args) { + return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index a3f7537f2..5660b913e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -7,6 +7,7 @@ import com.electronwill.nightconfig.toml.TomlFormat; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.gson.annotations.Expose; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; @@ -42,20 +43,20 @@ public class VelocityConfiguration implements ProxyConfig { private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class); - private String bind = "0.0.0.0:25577"; - private String motd = "&3A Velocity Server"; - private int showMaxPlayers = 500; - private boolean onlineMode = true; - private boolean preventClientProxyConnections = false; - private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; + @Expose private String bind = "0.0.0.0:25577"; + @Expose private String motd = "&3A Velocity Server"; + @Expose private int showMaxPlayers = 500; + @Expose private boolean onlineMode = true; + @Expose private boolean preventClientProxyConnections = false; + @Expose private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); - private boolean announceForge = false; - private boolean onlineModeKickExistingPlayers = false; - private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; + @Expose private boolean announceForge = false; + @Expose private boolean onlineModeKickExistingPlayers = false; + @Expose private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; private final Servers servers; private final ForcedHosts forcedHosts; - private final Advanced advanced; - private final Query query; + @Expose private final Advanced advanced; + @Expose private final Query query; private final Metrics metrics; private final Messages messages; private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent; @@ -622,18 +623,18 @@ public class VelocityConfiguration implements ProxyConfig { private static class Advanced { - private int compressionThreshold = 256; - private int compressionLevel = -1; - private int loginRatelimit = 3000; - private int connectionTimeout = 5000; - private int readTimeout = 30000; - private boolean proxyProtocol = false; - private boolean tcpFastOpen = false; - private boolean bungeePluginMessageChannel = true; - private boolean showPingRequests = false; - private boolean failoverOnUnexpectedServerDisconnect = true; - private boolean announceProxyCommands = true; - private boolean logCommandExecutions = false; + @Expose private int compressionThreshold = 256; + @Expose private int compressionLevel = -1; + @Expose private int loginRatelimit = 3000; + @Expose private int connectionTimeout = 5000; + @Expose private int readTimeout = 30000; + @Expose private boolean proxyProtocol = false; + @Expose private boolean tcpFastOpen = false; + @Expose private boolean bungeePluginMessageChannel = true; + @Expose private boolean showPingRequests = false; + @Expose private boolean failoverOnUnexpectedServerDisconnect = true; + @Expose private boolean announceProxyCommands = true; + @Expose private boolean logCommandExecutions = false; private Advanced() { } @@ -725,10 +726,10 @@ public class VelocityConfiguration implements ProxyConfig { private static class Query { - private boolean queryEnabled = false; - private int queryPort = 25577; - private String queryMap = "Velocity"; - private boolean showPlugins = false; + @Expose private boolean queryEnabled = false; + @Expose private int queryPort = 25577; + @Expose private String queryMap = "Velocity"; + @Expose private boolean showPlugins = false; private Query() { } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java new file mode 100644 index 000000000..ebc1dd015 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -0,0 +1,154 @@ +package com.velocitypowered.proxy.util; + +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.meta.PluginDependency; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.config.ProxyConfig; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.util.ProxyVersion; + +import io.netty.channel.unix.DomainSocketAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; +import joptsimple.internal.Strings; + +public enum InformationUtils { + ; + + /** + * Retrieves a {@link JsonArray} containing basic information about all + * running plugins on the {@link ProxyServer} instance. + * + * @param proxy the proxy instance to retrieve from + * @return {@link JsonArray} containing zero or more {@link JsonObject} + */ + public static JsonArray collectPluginInfo(ProxyServer proxy) { + List allPlugins = ImmutableList.copyOf( + proxy.getPluginManager().getPlugins()); + JsonArray plugins = new JsonArray(); + + for (PluginContainer plugin : allPlugins) { + PluginDescription desc = plugin.getDescription(); + JsonObject current = new JsonObject(); + current.addProperty("id", desc.getId()); + if (desc.getName().isPresent()) { + current.addProperty("name", desc.getName().get()); + } + if (desc.getVersion().isPresent()) { + current.addProperty("version", desc.getVersion().get()); + } + if (!desc.getAuthors().isEmpty()) { + current.addProperty("authors", + Strings.join(desc.getAuthors(), ",")); + } + if (desc.getDescription().isPresent()) { + current.addProperty("description", desc.getDescription().get()); + } + if (desc.getUrl().isPresent()) { + current.addProperty("url", desc.getUrl().get()); + } + if (!desc.getDependencies().isEmpty()) { + JsonArray dependencies = new JsonArray(); + for (PluginDependency dependency : desc.getDependencies()) { + dependencies.add(dependency.getId()); + } + current.add("dependencies", dependencies); + } + plugins.add(current); + } + return plugins; + } + + /** + * Creates a {@link JsonObject} containing information about the + * current environment the project is run under. + * + * @return {@link JsonObject} containing environment info + */ + public static JsonObject collectEnvironmentInfo() { + JsonObject envInfo = new JsonObject(); + envInfo.addProperty("operatingSystemType", System.getProperty("os.name")); + envInfo.addProperty("operatingSystemVersion", System.getProperty("os.version")); + envInfo.addProperty("operatingSystemArchitecture", System.getProperty("os.arch")); + envInfo.addProperty("javaVersion", System.getProperty("java.version")); + envInfo.addProperty("javaVendor", System.getProperty("java.vendor")); + return envInfo; + } + + /** + * Creates a {@link JsonObject} containing most relevant + * information of the {@link RegisteredServer} for diagnosis. + * + * @param server the server to evaluate + * @return {@link JsonObject} containing server and diagnostic info + */ + public static JsonObject collectServerInfo(RegisteredServer server) { + JsonObject info = new JsonObject(); + info.addProperty("currentPlayers", server.getPlayersConnected().size()); + SocketAddress address = server.getServerInfo().getAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress iaddr = (InetSocketAddress) address; + info.addProperty("socketType", "EventLoop"); + info.addProperty("unresolved", iaddr.isUnresolved()); + // Greetings form Netty 4aa10db9 + info.addProperty("host", iaddr.getHostString()); + info.addProperty("port", iaddr.getPort()); + } else if (address instanceof DomainSocketAddress) { + DomainSocketAddress daddr = (DomainSocketAddress) address; + info.addProperty("socketType", "Unix/Epoll"); + info.addProperty("host", daddr.path()); + } else { + info.addProperty("socketType", "Unknown/Generic"); + info.addProperty("host", address.toString()); + } + return info; + } + + /** + * Creates a {@link JsonObject} containing information about the + * current environment the project is run under. + * + * @param version the proxy instance to retrieve from + * @return {@link JsonObject} containing environment info + */ + public static JsonObject collectProxyInfo(ProxyVersion version) { + return (JsonObject) serializeObject(version, false); + } + + /** + * Creates a {@link JsonObject} containing most relevant + * information of the {@link ProxyConfig} for diagnosis. + * + * @param config the config instance to retrieve from + * @return {@link JsonObject} containing select config values + */ + public static JsonObject collectProxyConfig(ProxyConfig config) { + return (JsonObject) serializeObject(config, true); + } + + private static JsonElement serializeObject(Object toSerialize, boolean withExcludes) { + return JsonParser.parseString( + withExcludes ? GSON_WITH_EXCLUDES.toJson(toSerialize) : + GSON_WITHOUT_EXCLUDES.toJson(toSerialize)); + } + + private static final Gson GSON_WITH_EXCLUDES = new GsonBuilder() + .setPrettyPrinting() + .excludeFieldsWithoutExposeAnnotation() + .create(); + + private static final Gson GSON_WITHOUT_EXCLUDES = new GsonBuilder() + .setPrettyPrinting() + .create(); + + +} From 140eaaf5abe79270e6cf3bac25a8182524c27b8c Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sun, 25 Oct 2020 20:04:52 +0100 Subject: [PATCH 2/3] Velocity Dump WIP Part 2 --- .../command/builtin/VelocityCommand.java | 104 ++++++++++++++++- .../proxy/util/InformationUtils.java | 110 +++++++++++++++++- 2 files changed, 208 insertions(+), 6 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 52aa7468a..08ce75991 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -5,7 +5,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.io.CharStreams; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.permission.Tristate; @@ -17,6 +21,12 @@ import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.util.InformationUtils; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -320,9 +330,18 @@ public class VelocityCommand implements SimpleCommand { servers.add(iter.getServerInfo().getName(), InformationUtils.collectServerInfo(iter)); } + JsonArray connectOrder = new JsonArray(); + List attemptedConnectionOrder = ImmutableList.copyOf( + server.getConfiguration().getAttemptConnectionOrder()); + for (int i = 0; i < attemptedConnectionOrder.size(); i++) { + connectOrder.add(attemptedConnectionOrder.get(i)); + } JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.getConfiguration()); proxyConfig.add("servers", servers); + proxyConfig.add("connectOrder", connectOrder); + proxyConfig.add("forcedHosts", + InformationUtils.collectForcedHosts(server.getConfiguration())); JsonObject dump = new JsonObject(); dump.add("versionInfo", InformationUtils.collectProxyInfo(server.getVersion())); @@ -330,7 +349,90 @@ public class VelocityCommand implements SimpleCommand { dump.add("config", proxyConfig); dump.add("plugins", InformationUtils.collectPluginInfo(server)); - // TODO: Finish + source.sendMessage(Component.text().content("Uploading gathered information...").build()); + + HttpURLConnection upload = null; + try { + upload = (HttpURLConnection) new URL("https://dump.velocitypowered.com/documents") + .openConnection(); + } catch (IOException e1) { + // Couldn't open connection; + source.sendMessage( + Component.text() + .content("Failed to open a connection!") + .color(NamedTextColor.RED).build()); + return; + } + upload.setRequestProperty("Content-Type", "text/plain"); + upload.addRequestProperty( + "User-Agent", server.getVersion().getName() + "/" + + server.getVersion().getVersion()); + try { + upload.setRequestMethod("POST"); + upload.setDoOutput(true); + + OutputStream uploadStream = upload.getOutputStream(); + uploadStream.write( + InformationUtils.toHumanReadableString(dump).getBytes(StandardCharsets.UTF_8)); + uploadStream.close(); + } catch (IOException e2) { + // Couldn't POST the Data + source.sendMessage( + Component.text() + .content("Couldn't upload the data!") + .color(NamedTextColor.RED).build()); + return; + } + String rawResponse = null; + try { + rawResponse = CharStreams.toString( + new InputStreamReader(upload.getInputStream(), StandardCharsets.UTF_8)); + upload.getInputStream().close(); + } catch (IOException e3) { + // Couldn't read response + source.sendMessage( + Component.text() + .content("Invalid server response received!") + .color(NamedTextColor.RED).build()); + } + JsonObject returned = null; + try { + returned = InformationUtils.parseString(rawResponse); + if (returned == null || !returned.has("key")) { + throw new JsonParseException("Invalid json response"); + } + } catch (JsonSyntaxException e4) { + // Mangled json + source.sendMessage( + Component.text() + .content("Server responded with invalid data!") + .color(NamedTextColor.RED).build()); + return; + } catch (JsonParseException e5) { + // Backend error? + source.sendMessage( + Component.text() + .content("Data was uploaded successfully but couldn't be posted") + .color(NamedTextColor.RED).build()); + return; + } + TextComponent response = Component.text() + .content("Created an anonymised report containing useful information about") + .append(Component.newline() + .append( + Component.text("this proxy. If a developer requested it" + + ", you may share the")) + .append(Component.newline()) + .append(Component.text("following link with them:")) + .append(Component.newline()) + .append(Component.text("https://dump.velocitypowered.com/" + + returned.get("key").getAsString() + ".json") + .color(NamedTextColor.GREEN))) + .append(Component.newline()) + .append(Component.text("Note: This link is only valid for a few days")) + .build(); + source.sendMessage(response); + } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java index ebc1dd015..9016ae3ff 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -1,6 +1,7 @@ package com.velocitypowered.proxy.util; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -16,9 +17,15 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.ProxyVersion; import io.netty.channel.unix.DomainSocketAddress; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.List; +import java.util.Map; + import joptsimple.internal.Strings; public enum InformationUtils { @@ -84,6 +91,75 @@ public enum InformationUtils { return envInfo; } + /** + * Creates a {@link JsonObject} containing information about the + * forced hosts of the {@link ProxyConfig} instance. + * + * @return {@link JsonArray} containing forced hosts + */ + public static JsonObject collectForcedHosts(ProxyConfig config) { + JsonObject forcedHosts = new JsonObject(); + Map> allForcedHosts = ImmutableMap.copyOf( + config.getForcedHosts()); + for (Map.Entry> entry : allForcedHosts.entrySet()) { + JsonArray host = new JsonArray(); + for (int i = 0; i < entry.getValue().size(); i++) { + host.add(entry.getValue().get(i)); + } + forcedHosts.add(entry.getKey(), host); + } + return forcedHosts; + } + + /** + * Anonymises or redacts a given {@link InetAddress} + * public address bits. + * + * @param address The address to redact + * @return {@link String} address with public parts redacted + */ + public static String anonymizeInetAddress(InetAddress address) { + if (address instanceof Inet4Address) { + Inet4Address v4 = (Inet4Address) address; + if (v4.isAnyLocalAddress() || v4.isLoopbackAddress() + || v4.isLinkLocalAddress() + || v4.isSiteLocalAddress()) { + return address.getHostAddress(); + } else { + byte[] addr = v4.getAddress(); + return (addr[0] & 0xff) + "." + (addr[1] & 0xff) + ".XXX.XXX"; + } + } else if (address instanceof Inet6Address) { + Inet6Address v6 = (Inet6Address) address; + if (v6.isAnyLocalAddress() || v6.isLoopbackAddress() + || v6.isSiteLocalAddress() + || v6.isSiteLocalAddress()) { + return address.getHostAddress(); + } else { + String[] bits = v6.getHostAddress().split(":"); + String ret = ""; + boolean flag = false; + for (int iter = 0; iter < bits.length; iter++) { + if (flag) { + ret += ":X"; + continue; + } + if (!bits[iter].equals("0")) { + if (iter == 0) { + ret = bits[iter]; + } else { + ret = "::" + bits[iter]; + } + flag = true; + } + } + return ret; + } + } else { + return address.getHostAddress(); + } + } + /** * Creates a {@link JsonObject} containing most relevant * information of the {@link RegisteredServer} for diagnosis. @@ -97,18 +173,22 @@ public enum InformationUtils { SocketAddress address = server.getServerInfo().getAddress(); if (address instanceof InetSocketAddress) { InetSocketAddress iaddr = (InetSocketAddress) address; - info.addProperty("socketType", "EventLoop"); + info.addProperty("socketType", "EventLoop/NIO"); info.addProperty("unresolved", iaddr.isUnresolved()); - // Greetings form Netty 4aa10db9 - info.addProperty("host", iaddr.getHostString()); + if (iaddr.isUnresolved()) { + // Greetings form Netty 4aa10db9 + info.addProperty("host", iaddr.getHostString()); + } else { + info.addProperty("host", anonymizeInetAddress(iaddr.getAddress())); + } info.addProperty("port", iaddr.getPort()); } else if (address instanceof DomainSocketAddress) { DomainSocketAddress daddr = (DomainSocketAddress) address; info.addProperty("socketType", "Unix/Epoll"); - info.addProperty("host", daddr.path()); + info.addProperty("path", daddr.path()); } else { info.addProperty("socketType", "Unknown/Generic"); - info.addProperty("host", address.toString()); + info.addProperty("info", address.toString()); } return info; } @@ -135,6 +215,26 @@ public enum InformationUtils { return (JsonObject) serializeObject(config, true); } + /** + * Creates a human-readable String from a {@link JsonElement}. + * + * @param json the {@link JsonElement} object + * @return the human-readable String + */ + public static String toHumanReadableString(JsonElement json) { + return GSON_WITHOUT_EXCLUDES.toJson(json); + } + + /** + * Creates a {@link JsonObject} from a String. + * + * @param toParse the String to parse + * @return {@link JsonObject} object + */ + public static JsonObject parseString(String toParse) { + return GSON_WITHOUT_EXCLUDES.fromJson(toParse, JsonObject.class); + } + private static JsonElement serializeObject(Object toSerialize, boolean withExcludes) { return JsonParser.parseString( withExcludes ? GSON_WITH_EXCLUDES.toJson(toSerialize) : From 01070f8fd27643d229a16d1472b75f672cb33d82 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Tue, 27 Oct 2020 01:09:43 +0100 Subject: [PATCH 3/3] Velocity Dump Cleanup --- .../command/builtin/VelocityCommand.java | 178 +++++++++--------- .../proxy/util/InformationUtils.java | 4 - 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 08ce75991..3218960b9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -5,7 +5,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.io.CharStreams; +import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; @@ -21,17 +21,15 @@ import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.util.InformationUtils; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; +import java.net.ConnectException; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import net.kyori.adventure.identity.Identity; @@ -43,6 +41,10 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Response; import org.checkerframework.checker.nullness.qual.NonNull; public class VelocityCommand implements SimpleCommand { @@ -311,6 +313,7 @@ public class VelocityCommand implements SimpleCommand { private static class Dump implements SubCommand { + private static final Logger logger = LogManager.getLogger(Dump.class); private final ProxyServer server; private Dump(ProxyServer server) { @@ -350,89 +353,92 @@ public class VelocityCommand implements SimpleCommand { dump.add("plugins", InformationUtils.collectPluginInfo(server)); source.sendMessage(Component.text().content("Uploading gathered information...").build()); + AsyncHttpClient httpClient = ((VelocityServer) server).getAsyncHttpClient(); - HttpURLConnection upload = null; - try { - upload = (HttpURLConnection) new URL("https://dump.velocitypowered.com/documents") - .openConnection(); - } catch (IOException e1) { - // Couldn't open connection; - source.sendMessage( - Component.text() - .content("Failed to open a connection!") - .color(NamedTextColor.RED).build()); - return; - } - upload.setRequestProperty("Content-Type", "text/plain"); - upload.addRequestProperty( - "User-Agent", server.getVersion().getName() + "/" - + server.getVersion().getVersion()); - try { - upload.setRequestMethod("POST"); - upload.setDoOutput(true); + BoundRequestBuilder request = + httpClient.preparePost("https://dump.velocitypowered.com/documents"); + request.setHeader("Content-Type", "text/plain"); + request.addHeader("User-Agent", server.getVersion().getName() + "/" + + server.getVersion().getVersion()); + request.setBody( + InformationUtils.toHumanReadableString(dump).getBytes(StandardCharsets.UTF_8)); - OutputStream uploadStream = upload.getOutputStream(); - uploadStream.write( - InformationUtils.toHumanReadableString(dump).getBytes(StandardCharsets.UTF_8)); - uploadStream.close(); - } catch (IOException e2) { - // Couldn't POST the Data - source.sendMessage( - Component.text() - .content("Couldn't upload the data!") - .color(NamedTextColor.RED).build()); - return; - } - String rawResponse = null; - try { - rawResponse = CharStreams.toString( - new InputStreamReader(upload.getInputStream(), StandardCharsets.UTF_8)); - upload.getInputStream().close(); - } catch (IOException e3) { - // Couldn't read response - source.sendMessage( - Component.text() - .content("Invalid server response received!") - .color(NamedTextColor.RED).build()); - } - JsonObject returned = null; - try { - returned = InformationUtils.parseString(rawResponse); - if (returned == null || !returned.has("key")) { - throw new JsonParseException("Invalid json response"); + ListenableFuture future = request.execute(); + future.addListener(() -> { + try { + Response response = future.get(); + if (response.getStatusCode() != 200) { + source.sendMessage(Component.text() + .content("An error occurred while communicating with the Velocity servers. " + + "The servers may be temporarily unavailable or there is an issue " + + "with your network settings. You can find more information in the " + + "log or console of your Velocity server.") + .color(NamedTextColor.RED).build()); + logger.error("Invalid status code while POST-ing Velocity dump: " + + response.getStatusCode()); + logger.error("Headers: \n--------------BEGIN HEADERS--------------\n" + + response.getHeaders().toString() + + "\n---------------END HEADERS---------------"); + return; + } + JsonObject key = InformationUtils.parseString( + response.getResponseBody(StandardCharsets.UTF_8)); + if (!key.has("key")) { + throw new JsonSyntaxException("Missing Dump-Url-response"); + } + String url = "https://dump.velocitypowered.com/" + + key.get("key").getAsString() + ".json"; + source.sendMessage(Component.text() + .content("Created an anonymised report containing useful information about " + + "this proxy. If a developer requested it, you may share the " + + "following link with them:") + .append(Component.newline()) + .append(Component.text(">> " + url) + .color(NamedTextColor.GREEN) + .clickEvent(ClickEvent.openUrl(url))) + .append(Component.newline()) + .append(Component.text("Note: This link is only valid for a few days") + .color(NamedTextColor.GRAY) + ).build()); + } catch (InterruptedException e) { + source.sendMessage(Component.text() + .content("Could not complete the request, the command was interrupted." + + "Please refer to the proxy-log or console for more information.") + .color(NamedTextColor.RED).build()); + logger.error("Failed to complete dump command, " + + "the executor was interrupted: " + e.getMessage()); + e.printStackTrace(); + } catch (ExecutionException e) { + TextComponent.Builder message = Component.text() + .content("An error occurred while attempting to upload the gathered " + + "information to the Velocity servers.") + .append(Component.newline()) + .color(NamedTextColor.RED); + if (e.getCause() instanceof UnknownHostException + || e.getCause() instanceof ConnectException) { + message.append(Component.text( + "Likely cause: Invalid system DNS settings or no internet connection")); + } + source.sendMessage(message + .append(Component.newline() + .append(Component.text( + "Error details can be found in the proxy log / console")) + ).build()); + + logger.error("Failed to complete dump command, " + + "the executor encountered an Exception: " + e.getCause().getMessage()); + e.getCause().printStackTrace(); + } catch (JsonParseException e) { + source.sendMessage(Component.text() + .content("An error occurred on the Velocity-servers and the dump could not " + + "be completed. Please contact the Velocity staff about this problem. " + + "If you do, provide the details about this error from the Velocity " + + "console or server log.") + .color(NamedTextColor.RED).build()); + logger.error("Invalid response from the Velocity servers: " + e.getMessage()); + e.printStackTrace(); } - } catch (JsonSyntaxException e4) { - // Mangled json - source.sendMessage( - Component.text() - .content("Server responded with invalid data!") - .color(NamedTextColor.RED).build()); - return; - } catch (JsonParseException e5) { - // Backend error? - source.sendMessage( - Component.text() - .content("Data was uploaded successfully but couldn't be posted") - .color(NamedTextColor.RED).build()); - return; - } - TextComponent response = Component.text() - .content("Created an anonymised report containing useful information about") - .append(Component.newline() - .append( - Component.text("this proxy. If a developer requested it" - + ", you may share the")) - .append(Component.newline()) - .append(Component.text("following link with them:")) - .append(Component.newline()) - .append(Component.text("https://dump.velocitypowered.com/" - + returned.get("key").getAsString() + ".json") - .color(NamedTextColor.GREEN))) - .append(Component.newline()) - .append(Component.text("Note: This link is only valid for a few days")) - .build(); - source.sendMessage(response); - + }, MoreExecutors.directExecutor()); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java index 9016ae3ff..c75597f4d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -173,8 +173,6 @@ public enum InformationUtils { SocketAddress address = server.getServerInfo().getAddress(); if (address instanceof InetSocketAddress) { InetSocketAddress iaddr = (InetSocketAddress) address; - info.addProperty("socketType", "EventLoop/NIO"); - info.addProperty("unresolved", iaddr.isUnresolved()); if (iaddr.isUnresolved()) { // Greetings form Netty 4aa10db9 info.addProperty("host", iaddr.getHostString()); @@ -184,10 +182,8 @@ public enum InformationUtils { info.addProperty("port", iaddr.getPort()); } else if (address instanceof DomainSocketAddress) { DomainSocketAddress daddr = (DomainSocketAddress) address; - info.addProperty("socketType", "Unix/Epoll"); info.addProperty("path", daddr.path()); } else { - info.addProperty("socketType", "Unknown/Generic"); info.addProperty("info", address.toString()); } return info;