From 49ab2764c44b378657de13076b9142a1e830a576 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 12 Apr 2020 17:05:36 -0400 Subject: [PATCH 01/24] Close one last "proxy crasher" loophole --- .../proxy/connection/MinecraftConnection.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index d36846cc7..53c83310a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -145,7 +145,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } } - ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + installDiscardHandler(ctx); ctx.close(); } } @@ -161,6 +161,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { Preconditions.checkState(this.channel.eventLoop().inEventLoop(), "Not in event loop"); } + private void installDiscardHandler(ChannelHandlerContext ctx) { + ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + } + + private void installDiscardHandler() { + channel.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + } + public EventLoop eventLoop() { return channel.eventLoop(); } @@ -201,6 +209,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { public void closeWith(Object msg) { if (channel.isActive()) { knownDisconnect = true; + installDiscardHandler(); channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); } } @@ -210,6 +219,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { */ public void close() { if (channel.isActive()) { + installDiscardHandler(); channel.close(); } } From 4ddc55a5b79705d3d0764cdf91b153d0185a54e8 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 12 Apr 2020 17:20:38 -0400 Subject: [PATCH 02/24] No need to register multiple times --- .../proxy/connection/MinecraftConnection.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index 53c83310a..d3214787c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -162,11 +162,15 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } private void installDiscardHandler(ChannelHandlerContext ctx) { - ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + if (ctx.pipeline().get("discard") == null) { + ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + } } private void installDiscardHandler() { - channel.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + if (channel.pipeline().get("discard") == null) { + channel.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + } } public EventLoop eventLoop() { From e93a510b0f60a47b70bdd6fd4a8f05fc3bebe0d9 Mon Sep 17 00:00:00 2001 From: creeper123123321 <7974274+creeper123123321@users.noreply.github.com> Date: Mon, 13 Apr 2020 10:35:26 -0300 Subject: [PATCH 03/24] (Implementation) Allow injecting into backend connection --- .../velocitypowered/proxy/VelocityServer.java | 6 +++ .../backend/VelocityServerConnection.java | 17 +------ .../network/BackendChannelInitializer.java | 45 +++++++++++++++++++ .../BackendChannelInitializerHolder.java | 35 +++++++++++++++ .../proxy/network/ConnectionManager.java | 10 ++++- 5 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializer.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializerHolder.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index ad80a9161..cc92a4ae7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -44,6 +44,8 @@ import com.velocitypowered.proxy.util.bossbar.VelocityBossBar; import com.velocitypowered.proxy.util.ratelimit.Ratelimiter; import com.velocitypowered.proxy.util.ratelimit.Ratelimiters; import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import java.io.IOException; import java.net.InetSocketAddress; @@ -268,6 +270,10 @@ public class VelocityServer implements ProxyServer { return this.cm.createWorker(group); } + public ChannelInitializer getBackendChannelInitializer() { + return this.cm.backendChannelInitializerHolder.get(); + } + public boolean isShutdown() { return shutdown; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index a7cf3ed82..b91522507 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -80,22 +80,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, // Note: we use the event loop for the connection the player is on. This reduces context // switches. server.createBootstrap(proxyPlayer.getConnection().eventLoop()) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline() - .addLast(READ_TIMEOUT, - new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), - TimeUnit.MILLISECONDS)) - .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) - .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) - .addLast(FLOW_HANDLER, new FlowControlHandler()) - .addLast(MINECRAFT_DECODER, - new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND)) - .addLast(MINECRAFT_ENCODER, - new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND)); - } - }) + .handler(server.getBackendChannelInitializer()) .connect(registeredServer.getServerInfo().getAddress()) .addListener((ChannelFutureListener) future -> { if (future.isSuccess()) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializer.java b/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializer.java new file mode 100644 index 000000000..cb261fc67 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializer.java @@ -0,0 +1,45 @@ +package com.velocitypowered.proxy.network; + +import static com.velocitypowered.proxy.network.Connections.FLOW_HANDLER; +import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; +import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER; +import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT; + +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.handler.flow.FlowControlHandler; +import io.netty.handler.timeout.ReadTimeoutHandler; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("WeakerAccess") +public class BackendChannelInitializer extends ChannelInitializer { + + private final VelocityServer server; + + public BackendChannelInitializer(VelocityServer server) { + this.server = server; + } + + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline() + .addLast(READ_TIMEOUT, + new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), + TimeUnit.MILLISECONDS)) + .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) + .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) + .addLast(FLOW_HANDLER, new FlowControlHandler()) + .addLast(MINECRAFT_DECODER, + new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND)) + .addLast(MINECRAFT_ENCODER, + new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND)); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializerHolder.java b/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializerHolder.java new file mode 100644 index 000000000..7da1ab32d --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/BackendChannelInitializerHolder.java @@ -0,0 +1,35 @@ +package com.velocitypowered.proxy.network; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import java.util.function.Supplier; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class BackendChannelInitializerHolder implements Supplier> { + + private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); + private ChannelInitializer initializer; + + BackendChannelInitializerHolder(final ChannelInitializer initializer) { + this.initializer = initializer; + } + + @Override + public ChannelInitializer get() { + return this.initializer; + } + + /** + * Sets the channel initializer. + * + * @param initializer the new initializer to use + * @deprecated Internal implementation detail + */ + @Deprecated + public void set(final ChannelInitializer initializer) { + LOGGER.warn("The backend channel initializer has been replaced by {}", + Thread.currentThread().getStackTrace()[2]); + this.initializer = initializer; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index 765af2261..c0ebd11c5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -42,10 +42,12 @@ public final class ConnectionManager { private final EventLoopGroup bossGroup; private final EventLoopGroup workerGroup; private final VelocityServer server; - // This is intentionally made public for plugins like ViaVersion, which inject their own + // These are intentionally made public for plugins like ViaVersion, which inject their own // protocol logic into the proxy. @SuppressWarnings("WeakerAccess") public final ServerChannelInitializerHolder serverChannelInitializer; + @SuppressWarnings("WeakerAccess") + public final BackendChannelInitializerHolder backendChannelInitializerHolder; private final DnsAddressResolverGroup resolverGroup; private final AsyncHttpClient httpClient; @@ -62,6 +64,8 @@ public final class ConnectionManager { this.workerGroup = this.transportType.createEventLoopGroup(TransportType.Type.WORKER); this.serverChannelInitializer = new ServerChannelInitializerHolder( new ServerChannelInitializer(this.server)); + this.backendChannelInitializerHolder = new BackendChannelInitializerHolder( + new BackendChannelInitializer(this.server)); this.resolverGroup = new DnsAddressResolverGroup(new DnsNameResolverBuilder() .channelType(this.transportType.datagramChannelClass) .negativeTtl(15) @@ -204,4 +208,8 @@ public final class ConnectionManager { public AsyncHttpClient getHttpClient() { return httpClient; } + + public BackendChannelInitializerHolder getBackendChannelInitializerHolder() { + return this.backendChannelInitializerHolder; + } } From 9f6d7c2e82e4539e5e9b64a3474ca349908c4147 Mon Sep 17 00:00:00 2001 From: creeper123123321 <7974274+creeper123123321@users.noreply.github.com> Date: Mon, 13 Apr 2020 11:57:12 -0300 Subject: [PATCH 04/24] fix name --- .../java/com/velocitypowered/proxy/VelocityServer.java | 2 +- .../velocitypowered/proxy/network/ConnectionManager.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index cc92a4ae7..31ef34609 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -271,7 +271,7 @@ public class VelocityServer implements ProxyServer { } public ChannelInitializer getBackendChannelInitializer() { - return this.cm.backendChannelInitializerHolder.get(); + return this.cm.backendChannelInitializer.get(); } public boolean isShutdown() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index c0ebd11c5..649830544 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -47,7 +47,7 @@ public final class ConnectionManager { @SuppressWarnings("WeakerAccess") public final ServerChannelInitializerHolder serverChannelInitializer; @SuppressWarnings("WeakerAccess") - public final BackendChannelInitializerHolder backendChannelInitializerHolder; + public final BackendChannelInitializerHolder backendChannelInitializer; private final DnsAddressResolverGroup resolverGroup; private final AsyncHttpClient httpClient; @@ -64,7 +64,7 @@ public final class ConnectionManager { this.workerGroup = this.transportType.createEventLoopGroup(TransportType.Type.WORKER); this.serverChannelInitializer = new ServerChannelInitializerHolder( new ServerChannelInitializer(this.server)); - this.backendChannelInitializerHolder = new BackendChannelInitializerHolder( + this.backendChannelInitializer = new BackendChannelInitializerHolder( new BackendChannelInitializer(this.server)); this.resolverGroup = new DnsAddressResolverGroup(new DnsNameResolverBuilder() .channelType(this.transportType.datagramChannelClass) @@ -209,7 +209,7 @@ public final class ConnectionManager { return httpClient; } - public BackendChannelInitializerHolder getBackendChannelInitializerHolder() { - return this.backendChannelInitializerHolder; + public BackendChannelInitializerHolder getBackendChannelInitializer() { + return this.backendChannelInitializer; } } From d0cbcf65e92ef2f0cf150887d4ca8f14f7bbabbe Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 19 Apr 2020 09:07:17 -0400 Subject: [PATCH 05/24] Fix some tab-complete regressions --- .../proxy/command/VelocityCommandManager.java | 9 ++++++++- .../connection/client/ClientPlaySessionHandler.java | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index 4b85bdef6..a7742980c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -167,7 +167,14 @@ public class VelocityCommandManager implements CommandManager { if (line.isEmpty()) { return new String[0]; } - return line.trim().split(" ", -1); + + String[] trimmed = line.trim().split(" ", -1); + if (line.endsWith(" ") && !line.trim().isEmpty()) { + // To work around a 1.13+ specific bug we have to inject a space at the end of the arguments + trimmed = Arrays.copyOf(trimmed, trimmed.length + 1); + trimmed[trimmed.length - 1] = ""; + } + return trimmed; } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index f68448d8c..35d149c24 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -365,12 +365,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private boolean handleCommandTabComplete(TabCompleteRequest packet) { // In 1.13+, we need to do additional work for the richer suggestions available. String command = packet.getCommand().substring(1); - int spacePos = command.indexOf(' '); - if (spacePos == -1) { - spacePos = command.length(); + int commandEndPosition = command.indexOf(' '); + if (commandEndPosition == -1) { + commandEndPosition = command.length(); } - String commandLabel = command.substring(0, spacePos); + String commandLabel = command.substring(0, commandEndPosition); if (!server.getCommandManager().hasCommand(commandLabel)) { if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0) { // Outstanding tab completes are recorded for use with 1.12 clients and below to provide From 24c9fc0ef1f110ca8d44d5e7ce24279ae7d8563a Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 19 Apr 2020 09:20:03 -0400 Subject: [PATCH 06/24] Relax GameProfileRequestEvent#setGameProfile to allow faking even in online-mode --- .../api/event/player/GameProfileRequestEvent.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java index 6574f9d7b..4c209056a 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java @@ -60,13 +60,11 @@ public final class GameProfileRequestEvent { } /** - * Sets the game profile to use for this connection. It is invalid to use this method on an - * online-mode connection. + * Sets the game profile to use for this connection. * * @param gameProfile the profile for this connection, {@code null} uses the original profile */ public void setGameProfile(@Nullable GameProfile gameProfile) { - Preconditions.checkState(!onlineMode, "Profiles can not be faked in online mode!"); this.gameProfile = gameProfile; } From 2b84d35798a72f96427edf4cdf1c81bd17fa66e0 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Apr 2020 06:23:04 -0400 Subject: [PATCH 07/24] Fix bug in ConnectOther message Credits to @Jay113355 for spotting this --- .../connection/backend/BungeeCordMessageResponder.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java index 678c77e8f..911e803cd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java @@ -15,6 +15,7 @@ import com.velocitypowered.proxy.protocol.util.ByteBufDataOutput; import com.velocitypowered.proxy.server.VelocityRegisteredServer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import java.util.Optional; import java.util.StringJoiner; import net.kyori.text.serializer.legacy.LegacyComponentSerializer; @@ -43,8 +44,11 @@ class BungeeCordMessageResponder { String playerName = in.readUTF(); String serverName = in.readUTF(); - proxy.getPlayer(playerName).flatMap(player -> proxy.getServer(serverName)) - .ifPresent(server -> player.createConnectionRequest(server).fireAndForget()); + Optional referencedPlayer = proxy.getPlayer(playerName); + Optional referencedServer = proxy.getServer(serverName); + if (referencedPlayer.isPresent() && referencedServer.isPresent()) { + referencedPlayer.get().createConnectionRequest(referencedServer.get()).fireAndForget(); + } } private void processIp(ByteBufDataInput in) { From e6ea191c92dbc745f4884701b449830bd8d61042 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Apr 2020 10:27:08 -0400 Subject: [PATCH 08/24] Bump Netty to 4.1.49.Final This is intended to fix netty/netty#10165 directly, and supersede our current workaround. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2e57eb060..d3d19cdc9 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ allprojects { junitVersion = '5.3.0-M1' slf4jVersion = '1.7.25' log4jVersion = '2.11.2' - nettyVersion = '4.1.43.Final' + nettyVersion = '4.1.49.Final' guavaVersion = '25.1-jre' checkerFrameworkVersion = '2.7.0' configurateVersion = '3.6' From 81a0cbe3b9ff0373e598019f975b255bac50e636 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 19 Apr 2020 03:39:15 -0400 Subject: [PATCH 09/24] Fix rare race condition with transitioning If the player unexpectedly disconnects after ServerConnectEvent is fired, but before the connection transitions to the new player, Velocity would throw an exception thinking the connection was not present. This is the correct behavior, but the behavior is very surprising. Instead we will double-check to ensure the connection has not been lost before we continue with transitioning to the new server. --- .../proxy/connection/backend/TransitionSessionHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java index c5eb9d8d6..119e03785 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java @@ -83,6 +83,13 @@ public class TransitionSessionHandler implements MinecraftSessionHandler { .fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer(), existingConnection != null ? existingConnection.getServer() : null)) .whenCompleteAsync((x, error) -> { + // Make sure we can still transition (player might have disconnected here). + if (!serverConn.isActive()) { + // Connection is obsolete. + serverConn.disconnect(); + return; + } + // Strap on the ClientPlaySessionHandler if required. ClientPlaySessionHandler playHandler; if (serverConn.getPlayer().getConnection().getSessionHandler() From 957c0dd307b4a4f624ba24e95092c49813a35493 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 23 Apr 2020 16:19:49 -0400 Subject: [PATCH 10/24] Implement brigadier:long argument type, fixes #295 --- api/build.gradle | 1 + proxy/build.gradle | 2 +- .../brigadier/ArgumentPropertyRegistry.java | 3 ++ .../packet/brigadier/DummyProperty.java | 10 ++--- .../LongArgumentPropertySerializer.java | 40 +++++++++++++++++++ 5 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/LongArgumentPropertySerializer.java diff --git a/api/build.gradle b/api/build.gradle index edf3dc198..b156d61fc 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -63,6 +63,7 @@ artifacts { javadoc { options.encoding = 'UTF-8' options.charSet = 'UTF-8' + options.source = '8' options.links( 'http://www.slf4j.org/apidocs/', 'https://google.github.io/guava/releases/25.1-jre/api/docs/', diff --git a/proxy/build.gradle b/proxy/build.gradle index 216e58cc8..5498db10b 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -65,7 +65,7 @@ dependencies { compile 'it.unimi.dsi:fastutil:8.2.3' compile 'net.kyori:event-method-asm:3.0.0' - compile 'com.mojang:brigadier:1.0.15' + compile 'com.mojang:brigadier:1.0.17' compile 'org.asynchttpclient:async-http-client:2.10.4' diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index 9560a8b29..e7847ec8e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -4,6 +4,7 @@ import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgument import static com.velocitypowered.proxy.protocol.packet.brigadier.DummyVoidArgumentPropertySerializer.DUMMY; import static com.velocitypowered.proxy.protocol.packet.brigadier.FloatArgumentPropertySerializer.FLOAT; import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.INTEGER; +import static com.velocitypowered.proxy.protocol.packet.brigadier.LongArgumentPropertySerializer.LONG; import static com.velocitypowered.proxy.protocol.packet.brigadier.StringArgumentPropertySerializer.STRING; import com.mojang.brigadier.arguments.ArgumentType; @@ -11,6 +12,7 @@ import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.FloatArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; @@ -90,6 +92,7 @@ public class ArgumentPropertyRegistry { register("brigadier:double", DoubleArgumentType.class, DOUBLE); register("brigadier:bool", BoolArgumentType.class, VoidArgumentPropertySerializer.create(BoolArgumentType::bool)); + register("brigadier:long", LongArgumentType.class, LONG); // Minecraft argument types with extra properties dummy("minecraft:entity", ByteArgumentPropertySerializer.BYTE); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/DummyProperty.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/DummyProperty.java index 762b52686..9b514f903 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/DummyProperty.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/DummyProperty.java @@ -17,11 +17,6 @@ class DummyProperty implements ArgumentType { this.result = result; } - @Override - public T parse(StringReader reader) throws CommandSyntaxException { - throw new UnsupportedOperationException(); - } - public String getIdentifier() { return identifier; } @@ -33,4 +28,9 @@ class DummyProperty implements ArgumentType { public @Nullable T getResult() { return result; } + + @Override + public T parse(StringReader reader) { + throw new UnsupportedOperationException(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/LongArgumentPropertySerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/LongArgumentPropertySerializer.java new file mode 100644 index 000000000..9d3b40b6d --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/LongArgumentPropertySerializer.java @@ -0,0 +1,40 @@ +package com.velocitypowered.proxy.protocol.packet.brigadier; + +import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.HAS_MAXIMUM; +import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.HAS_MINIMUM; +import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags; + +import com.mojang.brigadier.arguments.LongArgumentType; +import io.netty.buffer.ByteBuf; + +class LongArgumentPropertySerializer implements ArgumentPropertySerializer { + + static final LongArgumentPropertySerializer LONG = new LongArgumentPropertySerializer(); + + private LongArgumentPropertySerializer() { + + } + + @Override + public LongArgumentType deserialize(ByteBuf buf) { + byte flags = buf.readByte(); + long minimum = (flags & HAS_MINIMUM) != 0 ? buf.readLong() : Long.MIN_VALUE; + long maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readLong() : Long.MAX_VALUE; + return LongArgumentType.longArg(minimum, maximum); + } + + @Override + public void serialize(LongArgumentType object, ByteBuf buf) { + boolean hasMinimum = object.getMinimum() != Long.MIN_VALUE; + boolean hasMaximum = object.getMaximum() != Long.MAX_VALUE; + byte flag = getFlags(hasMinimum, hasMaximum); + + buf.writeByte(flag); + if (hasMinimum) { + buf.writeLong(object.getMinimum()); + } + if (hasMaximum) { + buf.writeLong(object.getMaximum()); + } + } +} From 6555e0e337add5c2fc66ad8367a95a1c0c48ecfc Mon Sep 17 00:00:00 2001 From: Leymooo Date: Mon, 27 Apr 2020 00:04:12 +0300 Subject: [PATCH 11/24] Add CommandExecuteEvent --- .../api/command/CommandManager.java | 11 ++ .../event/command/CommandExecuteEvent.java | 143 ++++++++++++++++++ .../api/event/player/PlayerChatEvent.java | 10 +- .../velocitypowered/proxy/VelocityServer.java | 3 +- .../proxy/command/VelocityCommandManager.java | 38 +++++ .../client/ClientPlaySessionHandler.java | 38 +++-- .../proxy/console/VelocityConsole.java | 2 +- 7 files changed, 226 insertions(+), 19 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index 81a7b8297..ce3452e40 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -35,10 +35,21 @@ public interface CommandManager { /** * Attempts to execute a command from the specified {@code cmdLine}. + * CommandExecuteEvent will not called * * @param source the command's source * @param cmdLine the command to run * @return true if the command was found and executed, false if it was not */ boolean execute(CommandSource source, String cmdLine); + + /** + * Attempts to execute a command from the specified {@code cmdLine}. + * + * @param source the command's source + * @param cmdLine the command to run + * @param callEvent will CommandExecuteEvent called or not + * @return true if the command was found and executed, false if it was not + */ + boolean execute(CommandSource source, String cmdLine, boolean callEvent); } diff --git a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java new file mode 100644 index 000000000..f92ed6892 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java @@ -0,0 +1,143 @@ +package com.velocitypowered.api.event.command; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult; +import java.util.Optional; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * This event is fired when a player types in a chat message. + */ +public final class CommandExecuteEvent implements ResultedEvent { + + private final CommandSource commandSource; + private final String command; + private CommandResult result; + + /** + * Constructs a PlayerChatEvent. + * @param commandSource the source executing the command + * @param command the command being executed without first slash + */ + public CommandExecuteEvent(CommandSource commandSource, String command) { + this.commandSource = Preconditions.checkNotNull(commandSource, "commandSource"); + this.command = Preconditions.checkNotNull(command, "command"); + this.result = CommandResult.allowed(); + } + + public CommandSource getCommandSource() { + return commandSource; + } + + /** + * Gets the original command being executed without first slash. + * @return the original command being executed + */ + public String getCommand() { + return command; + } + + @Override + public CommandResult getResult() { + return result; + } + + @Override + public void setResult(CommandResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + @Override + public String toString() { + return "PlayerCommmandEvent{" + + "commandSource=" + commandSource + + ", command=" + command + + ", result=" + result + + '}'; + } + + /** + * Represents the result of the {@link CommandExecuteEvent}. + */ + public static final class CommandResult implements Result { + + private static final CommandResult ALLOWED = new CommandResult(true, false,null); + private static final CommandResult DENIED = new CommandResult(false, false,null); + private static final CommandResult FORWARD_TO_SERVER = new CommandResult(false, true, null); + + private @Nullable String command; + private final boolean status; + private final boolean forward; + + private CommandResult(boolean status, boolean forward, @Nullable String command) { + this.status = status; + this.forward = forward; + this.command = command; + } + + public Optional getCommand() { + return Optional.ofNullable(command); + } + + public boolean isForwardToServer() { + return forward; + } + + @Override + public boolean isAllowed() { + return status; + } + + @Override + public String toString() { + return status ? "allowed" : "denied"; + } + + /** + * Allows the command to be sent, without modification. + * @return the allowed result + */ + public static CommandResult allowed() { + return ALLOWED; + } + + /** + * Prevents the command from being executed. + * @return the denied result + */ + public static CommandResult denied() { + return DENIED; + } + + /** + * Prevents the command from being executed, but forward command to server. + * @return the forward result + */ + public static CommandResult forwardToServer() { + return FORWARD_TO_SERVER; + } + + /** + * Prevents the command from being executed on proxy, but forward command to server. + * @param newCommand the command without first slash to use instead + * @return a result with a new command being forwarded to server + */ + public static CommandResult forwardToServer(@NonNull String newCommand) { + Preconditions.checkNotNull(newCommand, "newCommand"); + return new CommandResult(false, true, newCommand); + } + + /** + * Allows the command to be executed, but silently replaced old command with another. + * @param newCommand the command to use instead without first slash + * @return a result with a new command + */ + public static CommandResult command(@NonNull String newCommand) { + Preconditions.checkNotNull(newCommand, "newCommand"); + return new CommandResult(true, false, newCommand); + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerChatEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChatEvent.java index 6b8b7bba1..ef77c96a4 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/PlayerChatEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChatEvent.java @@ -70,6 +70,10 @@ public final class PlayerChatEvent implements ResultedEvent getMessage() { + return Optional.ofNullable(message); + } + @Override public boolean isAllowed() { return status; @@ -96,10 +100,6 @@ public final class PlayerChatEvent implements ResultedEvent getMessage() { - return Optional.ofNullable(message); - } - /** * Allows the message to be sent, but silently replaced with another. * @param message the message to use instead @@ -110,6 +110,4 @@ public final class PlayerChatEvent implements ResultedEvent commands = new HashMap<>(); + private final EventManager eventManager; + + public VelocityCommandManager(EventManager eventManager) { + this.eventManager = eventManager; + } @Override @Deprecated @@ -47,11 +57,39 @@ public class VelocityCommandManager implements CommandManager { this.commands.remove(alias.toLowerCase(Locale.ENGLISH)); } + /** + * Calls CommandExecuteEvent. + * @param source the command's source + * @param cmd the command + * @return CompletableFuture of event + */ + public CompletableFuture callCommandEvent(CommandSource source, String cmd) { + Preconditions.checkNotNull(source, "invoker"); + Preconditions.checkNotNull(cmd, "cmd"); + return eventManager.fire(new CommandExecuteEvent(source, cmd)); + } + @Override public boolean execute(CommandSource source, String cmdLine) { Preconditions.checkNotNull(source, "invoker"); Preconditions.checkNotNull(cmdLine, "cmdLine"); + return execute(source, cmdLine, false); + } + + @Override + public boolean execute(CommandSource source, String cmdLine, boolean callEvent) { + Preconditions.checkNotNull(source, "invoker"); + Preconditions.checkNotNull(cmdLine, "cmdLine"); + + if (callEvent) { + CommandExecuteEvent event = callCommandEvent(source, cmdLine).join(); + CommandResult commandResult = event.getResult(); + if (commandResult.isForwardToServer() || !commandResult.isAllowed()) { + return false; + } + cmdLine = commandResult.getCommand().orElse(event.getCommand()); + } String alias = cmdLine; String args = ""; int firstSpace = cmdLine.indexOf(' '); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 35d149c24..f1bbaf1c8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -4,6 +4,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.constructChannelsPacket; +import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.player.PlayerChatEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; @@ -123,17 +124,32 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { String msg = packet.getMessage(); if (msg.startsWith("/")) { - try { - if (!server.getCommandManager().execute(player, msg.substring(1))) { - return false; - } - } catch (Exception e) { - logger.info("Exception occurred while running command for {}", player.getUsername(), - e); - player.sendMessage( - TextComponent.of("An error occurred while running this command.", TextColor.RED)); - return true; - } + + server.getCommandManager().callCommandEvent(player, msg.substring(1)) + .thenAcceptAsync(event -> { + CommandExecuteEvent.CommandResult commandResult = event.getResult(); + if (commandResult.isAllowed()) { + Optional eventCommand = event.getResult().getCommand(); + String command = eventCommand.orElse(event.getCommand()); + + if (commandResult.isForwardToServer()) { + smc.write(Chat.createServerbound(command)); + return; + } + + try { + if (!server.getCommandManager().execute(player, command)) { + smc.write(Chat.createServerbound(command)); + } + } catch (Exception e) { + logger.info("Exception occurred while running command for {}", player.getUsername(), + e); + player.sendMessage( + TextComponent.of("An error occurred while running this command.", + TextColor.RED)); + } + } + }); } else { PlayerChatEvent event = new PlayerChatEvent(player, msg); server.getEventManager().fire(event) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index 4b9d38aee..0123db3e1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -91,7 +91,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons @Override protected void runCommand(String command) { try { - if (!this.server.getCommandManager().execute(this, command)) { + if (!this.server.getCommandManager().execute(this, command, true)) { sendMessage(TextComponent.of("Command not found.", TextColor.RED)); } } catch (Exception e) { From 1fb382e0f6b4fcb7c2100cabe0a284ce01862e89 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Mon, 27 Apr 2020 00:09:25 +0300 Subject: [PATCH 12/24] fix logic --- .../client/ClientPlaySessionHandler.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index f1bbaf1c8..9c22e9c2c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -128,15 +128,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { server.getCommandManager().callCommandEvent(player, msg.substring(1)) .thenAcceptAsync(event -> { CommandExecuteEvent.CommandResult commandResult = event.getResult(); + Optional eventCommand = event.getResult().getCommand(); + String command = eventCommand.orElse(event.getCommand()); + if (commandResult.isForwardToServer()) { + smc.write(Chat.createServerbound(command)); + return; + } if (commandResult.isAllowed()) { - Optional eventCommand = event.getResult().getCommand(); - String command = eventCommand.orElse(event.getCommand()); - - if (commandResult.isForwardToServer()) { - smc.write(Chat.createServerbound(command)); - return; - } - try { if (!server.getCommandManager().execute(player, command)) { smc.write(Chat.createServerbound(command)); From fb64333813699b4f1ade5c9a0646f8d4f7460713 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Mon, 27 Apr 2020 00:11:49 +0300 Subject: [PATCH 13/24] pass eventLoop to thenApplyAsync --- .../proxy/connection/client/ClientPlaySessionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 9c22e9c2c..4864bcf22 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -147,7 +147,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { TextColor.RED)); } } - }); + }, smc.eventLoop()); } else { PlayerChatEvent event = new PlayerChatEvent(player, msg); server.getEventManager().fire(event) From c0b8e9d646cf94877c89f0b6d631ebb484f58ca2 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Mon, 27 Apr 2020 01:05:57 +0300 Subject: [PATCH 14/24] add async methods, also add separate methods which will call event and which will not call event --- .../api/command/CommandManager.java | 32 +++++++++-- .../proxy/command/VelocityCommandManager.java | 57 ++++++++++++++----- .../client/ClientPlaySessionHandler.java | 2 +- .../proxy/console/VelocityConsole.java | 4 +- .../proxy/plugin/VelocityEventManager.java | 4 ++ 5 files changed, 79 insertions(+), 20 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index ce3452e40..af200cb74 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -1,5 +1,7 @@ package com.velocitypowered.api.command; +import java.util.concurrent.CompletableFuture; + /** * Represents an interface to register a command executor with the proxy. */ @@ -34,8 +36,8 @@ public interface CommandManager { void unregister(String alias); /** - * Attempts to execute a command from the specified {@code cmdLine}. - * CommandExecuteEvent will not called + * Calls CommandExecuteEvent and attempts to execute a command from the specified {@code cmdLine} + * sync. * * @param source the command's source * @param cmdLine the command to run @@ -44,12 +46,32 @@ public interface CommandManager { boolean execute(CommandSource source, String cmdLine); /** - * Attempts to execute a command from the specified {@code cmdLine}. + * Attempts to execute a command from the specified {@code cmdLine} sync + * without calling CommandExecuteEvent. * * @param source the command's source * @param cmdLine the command to run - * @param callEvent will CommandExecuteEvent called or not * @return true if the command was found and executed, false if it was not */ - boolean execute(CommandSource source, String cmdLine, boolean callEvent); + boolean executeImmediately(CommandSource source, String cmdLine); + + /** + * Calls CommandExecuteEvent and attempts to execute a command from the specified {@code cmdLine} + * async. + * + * @param source the command's source + * @param cmdLine the command to run + * @return A future that will be completed with the result of the command execution + */ + CompletableFuture executeAsync(CommandSource source, String cmdLine); + + /** + * Attempts to execute a command from the specified {@code cmdLine} async + * without calling CommandExecuteEvent. + * + * @param source the command's source + * @param cmdLine the command to run + * @return A future that will be completed with the result of the command execution + */ + CompletableFuture executeImmediatelyAsync(CommandSource source, String cmdLine); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index 93caa314b..3a8a53201 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -7,9 +7,9 @@ import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.RawCommand; -import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult; +import com.velocitypowered.proxy.plugin.VelocityEventManager; import java.util.Arrays; import java.util.HashMap; @@ -22,9 +22,9 @@ import java.util.concurrent.CompletableFuture; public class VelocityCommandManager implements CommandManager { private final Map commands = new HashMap<>(); - private final EventManager eventManager; + private final VelocityEventManager eventManager; - public VelocityCommandManager(EventManager eventManager) { + public VelocityCommandManager(VelocityEventManager eventManager) { this.eventManager = eventManager; } @@ -74,22 +74,21 @@ public class VelocityCommandManager implements CommandManager { Preconditions.checkNotNull(source, "invoker"); Preconditions.checkNotNull(cmdLine, "cmdLine"); - return execute(source, cmdLine, false); + CommandExecuteEvent event = callCommandEvent(source, cmdLine).join(); + CommandResult commandResult = event.getResult(); + if (commandResult.isForwardToServer() || !commandResult.isAllowed()) { + return false; + } + cmdLine = commandResult.getCommand().orElse(event.getCommand()); + + return executeImmediately(source, cmdLine); } @Override - public boolean execute(CommandSource source, String cmdLine, boolean callEvent) { + public boolean executeImmediately(CommandSource source, String cmdLine) { Preconditions.checkNotNull(source, "invoker"); Preconditions.checkNotNull(cmdLine, "cmdLine"); - if (callEvent) { - CommandExecuteEvent event = callCommandEvent(source, cmdLine).join(); - CommandResult commandResult = event.getResult(); - if (commandResult.isForwardToServer() || !commandResult.isAllowed()) { - return false; - } - cmdLine = commandResult.getCommand().orElse(event.getCommand()); - } String alias = cmdLine; String args = ""; int firstSpace = cmdLine.indexOf(' '); @@ -113,6 +112,38 @@ public class VelocityCommandManager implements CommandManager { } } + + @Override + public CompletableFuture executeAsync(CommandSource source, String cmdLine) { + CompletableFuture result = new CompletableFuture<>(); + callCommandEvent(source, cmdLine).thenAccept(event -> { + CommandResult commandResult = event.getResult(); + if (commandResult.isForwardToServer() || !commandResult.isAllowed()) { + result.complete(false); + } + String command = commandResult.getCommand().orElse(event.getCommand()); + try { + result.complete(executeImmediately(source, command)); + } catch (Exception e) { + result.completeExceptionally(e); + } + }); + return result; + } + + @Override + public CompletableFuture executeImmediatelyAsync(CommandSource source, String cmdLine) { + CompletableFuture result = new CompletableFuture<>(); + eventManager.getService().execute(() -> { + try { + result.complete(executeImmediately(source, cmdLine)); + } catch (Exception e) { + result.completeExceptionally(e); + } + }); + return result; + } + public boolean hasCommand(String command) { return commands.containsKey(command); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 4864bcf22..e8b747bac 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -136,7 +136,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } if (commandResult.isAllowed()) { try { - if (!server.getCommandManager().execute(player, command)) { + if (!server.getCommandManager().executeImmediately(player, command)) { smc.write(Chat.createServerbound(command)); } } catch (Exception e) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index 0123db3e1..1ccc0c101 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -8,6 +8,8 @@ import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.proxy.VelocityServer; import java.util.List; +import java.util.concurrent.CompletableFuture; + import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; @@ -91,7 +93,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons @Override protected void runCommand(String command) { try { - if (!this.server.getCommandManager().execute(this, command, true)) { + if (!this.server.getCommandManager().execute(this, command)) { sendMessage(TextComponent.of("Command not found.", TextColor.RED)); } } catch (Exception e) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java index 2ef08dade..87b0097b6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java @@ -185,6 +185,10 @@ public class VelocityEventManager implements EventManager { fireEvent(new ProxyShutdownEvent()); } + public ExecutorService getService() { + return service; + } + private static class VelocityMethodScanner implements MethodScanner { @Override From 26bf94f08f3c4c25304ad023392f348c1bf85170 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Mon, 27 Apr 2020 13:09:04 +0300 Subject: [PATCH 15/24] fix typos, update javadocs --- .../api/command/CommandManager.java | 21 +++++++++++++------ .../event/command/CommandExecuteEvent.java | 4 ++-- .../proxy/command/VelocityCommandManager.java | 8 ++++--- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index af200cb74..f29777d6f 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -36,22 +36,29 @@ public interface CommandManager { void unregister(String alias); /** - * Calls CommandExecuteEvent and attempts to execute a command from the specified {@code cmdLine} - * sync. + * Calls CommandExecuteEvent and attempts to execute a command using the specified {@code cmdLine} + * in a blocking fashion. * * @param source the command's source * @param cmdLine the command to run * @return true if the command was found and executed, false if it was not + * + * @deprecated This method will block current thread during event call and command execution. + * Prefer {@link #executeAsync(CommandSource, String)} instead. */ + @Deprecated boolean execute(CommandSource source, String cmdLine); /** - * Attempts to execute a command from the specified {@code cmdLine} sync - * without calling CommandExecuteEvent. + * Attempts to execute a command using the specified {@code cmdLine} in a blocking fashion without + * calling CommandExecuteEvent. * * @param source the command's source * @param cmdLine the command to run * @return true if the command was found and executed, false if it was not + * + * @deprecated This method will block current thread during event and command execution. + * Prefer {@link #executeImmediatelyAsync(CommandSource, String)} instead. */ boolean executeImmediately(CommandSource source, String cmdLine); @@ -61,7 +68,8 @@ public interface CommandManager { * * @param source the command's source * @param cmdLine the command to run - * @return A future that will be completed with the result of the command execution + * @return A future that will be completed with the result of the command execution. + * Can be completed exceptionally if exception was thrown during execution. */ CompletableFuture executeAsync(CommandSource source, String cmdLine); @@ -71,7 +79,8 @@ public interface CommandManager { * * @param source the command's source * @param cmdLine the command to run - * @return A future that will be completed with the result of the command execution + * @return A future that will be completed with the result of the command execution. + * Can be completed exceptionally if exception was thrown during execution. */ CompletableFuture executeImmediatelyAsync(CommandSource source, String cmdLine); } diff --git a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java index f92ed6892..054699568 100644 --- a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java @@ -18,7 +18,7 @@ public final class CommandExecuteEvent implements ResultedEvent { private CommandResult result; /** - * Constructs a PlayerChatEvent. + * Constructs a CommandExecuteEvent. * @param commandSource the source executing the command * @param command the command being executed without first slash */ @@ -52,7 +52,7 @@ public final class CommandExecuteEvent implements ResultedEvent { @Override public String toString() { - return "PlayerCommmandEvent{" + return "CommandExecuteEvent{" + "commandSource=" + commandSource + ", command=" + command + ", result=" + result diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index 3a8a53201..2b3975f1a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -64,14 +64,14 @@ public class VelocityCommandManager implements CommandManager { * @return CompletableFuture of event */ public CompletableFuture callCommandEvent(CommandSource source, String cmd) { - Preconditions.checkNotNull(source, "invoker"); + Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(cmd, "cmd"); return eventManager.fire(new CommandExecuteEvent(source, cmd)); } @Override public boolean execute(CommandSource source, String cmdLine) { - Preconditions.checkNotNull(source, "invoker"); + Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(cmdLine, "cmdLine"); CommandExecuteEvent event = callCommandEvent(source, cmdLine).join(); @@ -86,7 +86,7 @@ public class VelocityCommandManager implements CommandManager { @Override public boolean executeImmediately(CommandSource source, String cmdLine) { - Preconditions.checkNotNull(source, "invoker"); + Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(cmdLine, "cmdLine"); String alias = cmdLine; @@ -133,6 +133,8 @@ public class VelocityCommandManager implements CommandManager { @Override public CompletableFuture executeImmediatelyAsync(CommandSource source, String cmdLine) { + Preconditions.checkNotNull(source, "source"); + Preconditions.checkNotNull(cmdLine, "cmdLine"); CompletableFuture result = new CompletableFuture<>(); eventManager.getService().execute(() -> { try { From 9d6689f64aefafdcbc0c96c30ec3cc93d1a77283 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Mon, 27 Apr 2020 13:12:03 +0300 Subject: [PATCH 16/24] forgot annotation --- .../java/com/velocitypowered/api/command/CommandManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index f29777d6f..402167151 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -60,6 +60,7 @@ public interface CommandManager { * @deprecated This method will block current thread during event and command execution. * Prefer {@link #executeImmediatelyAsync(CommandSource, String)} instead. */ + @Deprecated boolean executeImmediately(CommandSource source, String cmdLine); /** From 37a1a49fda5b513940c244a37984b38d619a982e Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 28 Apr 2020 14:10:54 -0400 Subject: [PATCH 17/24] Allow disabling BungeeCord plugin message channel. --- .../proxy/config/VelocityConfiguration.java | 14 ++++++++++++++ .../backend/BungeeCordMessageResponder.java | 4 ++++ 2 files changed, 18 insertions(+) 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 806ef44ce..67d9fcd7f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -402,6 +402,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi return pingPassthrough; } + public boolean isBungeePluginChannelEnabled() { + return advanced.isBungeePluginMessageChannel(); + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -654,6 +658,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi @ConfigKey("tcp-fast-open") private boolean tcpFastOpen = false; + @Comment("Enables BungeeCord plugin messaging channel support on Velocity.") + @ConfigKey("bungee-plugin-message-channel") + private boolean bungeePluginMessageChannel = true; + private Advanced() { } @@ -666,6 +674,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi this.readTimeout = toml.getLong("read-timeout", 30000L).intValue(); this.proxyProtocol = toml.getBoolean("proxy-protocol", false); this.tcpFastOpen = toml.getBoolean("tcp-fast-open", false); + this.bungeePluginMessageChannel = toml.getBoolean("bungee-plugin-message-channel", true); } } @@ -697,6 +706,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi return tcpFastOpen; } + public boolean isBungeePluginMessageChannel() { + return bungeePluginMessageChannel; + } + @Override public String toString() { return "Advanced{" @@ -707,6 +720,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi + ", readTimeout=" + readTimeout + ", proxyProtocol=" + proxyProtocol + ", tcpFastOpen=" + tcpFastOpen + + ", bungeePluginMessageChannel=" + bungeePluginMessageChannel + '}'; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java index 911e803cd..deb5b2304 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java @@ -273,6 +273,10 @@ class BungeeCordMessageResponder { } boolean process(PluginMessage message) { + if (!proxy.getConfiguration().isBungeePluginChannelEnabled()) { + return false; + } + if (!MODERN_CHANNEL.getId().equals(message.getChannel()) && !LEGACY_CHANNEL.getId() .equals(message.getChannel())) { return false; From aa55af8eb715ec512461e7c59cf66e36588a20dc Mon Sep 17 00:00:00 2001 From: Leymooo Date: Wed, 29 Apr 2020 22:08:59 +0300 Subject: [PATCH 18/24] fix command forwarding --- .../proxy/connection/client/ClientPlaySessionHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index e8b747bac..90e7ee6bb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -131,13 +131,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { Optional eventCommand = event.getResult().getCommand(); String command = eventCommand.orElse(event.getCommand()); if (commandResult.isForwardToServer()) { - smc.write(Chat.createServerbound(command)); + smc.write(Chat.createServerbound("/" + command)); return; } if (commandResult.isAllowed()) { try { if (!server.getCommandManager().executeImmediately(player, command)) { - smc.write(Chat.createServerbound(command)); + smc.write(Chat.createServerbound("/" + command)); } } catch (Exception e) { logger.info("Exception occurred while running command for {}", player.getUsername(), From d096eb342992568f780ebd287b6541a411115589 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Wed, 29 Apr 2020 22:15:33 +0300 Subject: [PATCH 19/24] update class javadoc --- .../velocitypowered/api/event/command/CommandExecuteEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java index 054699568..8181e6a3e 100644 --- a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java @@ -9,7 +9,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; /** - * This event is fired when a player types in a chat message. + * This event is fired when someone executing command */ public final class CommandExecuteEvent implements ResultedEvent { From 4b4ed02e8dd4720a9f99a3f8d868b8c71af81cee Mon Sep 17 00:00:00 2001 From: Leymooo Date: Thu, 30 Apr 2020 01:00:12 +0300 Subject: [PATCH 20/24] fix build --- .../velocitypowered/api/event/command/CommandExecuteEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java index 8181e6a3e..d08a323f6 100644 --- a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java @@ -9,7 +9,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; /** - * This event is fired when someone executing command + * This event is fired when someone executing command. */ public final class CommandExecuteEvent implements ResultedEvent { From 3bc8323ed57085814e2782d8190487bc64d13c56 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 8 May 2020 14:03:30 -0400 Subject: [PATCH 21/24] We're well into 2020, just saying. --- .../java/com/velocitypowered/proxy/command/VelocityCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java index 1c3b777c3..ec245152e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -166,7 +166,7 @@ public class VelocityCommand implements Command { .append(TextComponent.of(version.getVersion()).decoration(TextDecoration.BOLD, false)) .build(); TextComponent copyright = TextComponent - .of("Copyright 2018-2019 " + version.getVendor() + ". " + version.getName() + .of("Copyright 2018-2020 " + version.getVendor() + ". " + version.getName() + " is freely licensed under the terms of the MIT License."); source.sendMessage(velocity); source.sendMessage(copyright); From 21f03d5d504ad0e1e222acbeb311875e7857d70b Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Fri, 8 May 2020 03:03:49 +0300 Subject: [PATCH 22/24] Add prevent-proxy-connections option to make sending client IP to Mojang toggleable --- .../api/proxy/config/ProxyConfig.java | 9 +++++++++ .../proxy/config/VelocityConfiguration.java | 13 +++++++++++++ .../connection/client/LoginSessionHandler.java | 9 ++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java b/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java index 7b4d0bae2..6e5de2371 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java @@ -62,6 +62,15 @@ public interface ProxyConfig { */ boolean isOnlineMode(); + /** + * If client's ISP/AS sent from this proxy is different from the one from Mojang's + * authentication server, the player is kicked. This disallows some VPN and proxy + * connections but is a weak form of protection. + * + * @return whether to prevent client proxy connections by checking the IP with Mojang servers + */ + boolean shouldPreventClientProxyConnections(); + /** * Get a Map of all servers registered in velocity.toml. This method does * not return all the servers currently in memory, although in most cases it 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 67d9fcd7f..3b83c4380 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -54,6 +54,14 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi @ConfigKey("online-mode") private boolean onlineMode = true; + @Comment({ + "If client's ISP/AS sent from this proxy is different from the one from Mojang's", + "authentication server, the player is kicked. This disallows some VPN and proxy", + "connections but is a weak form of protection." + }) + @ConfigKey("prevent-client-proxy-connections") + private boolean preventClientProxyConnections = false; + @Comment({ "Should we forward IP addresses and other data to backend servers?", "Available options:", @@ -328,6 +336,11 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi return onlineMode; } + @Override + public boolean shouldPreventClientProxyConnections() { + return preventClientProxyConnections; + } + public PlayerInfoForwarding getPlayerInfoForwardingMode() { return playerInfoForwardingMode; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index 6fb58ffea..e367174c9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -50,7 +50,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); private static final String MOJANG_HASJOINED_URL = - "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; + "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s"; private final VelocityServer server; private final MinecraftConnection mcConnection; @@ -96,8 +96,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler { String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString(); String url = String.format(MOJANG_HASJOINED_URL, - urlFormParameterEscaper().escape(login.getUsername()), serverId, - urlFormParameterEscaper().escape(playerIp)); + urlFormParameterEscaper().escape(login.getUsername()), serverId); + + if (server.getConfiguration().shouldPreventClientProxyConnections()) { + url += "&ip=" + urlFormParameterEscaper().escape(playerIp); + } ListenableFuture hasJoinedResponse = server.getAsyncHttpClient().prepareGet(url) .execute(); From feb7c2f893d8438449a49c7cfd25bfaa080f2832 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 8 May 2020 18:16:14 -0400 Subject: [PATCH 23/24] Reject invalid tab complete command requests. --- .../proxy/protocol/packet/TabCompleteRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java index e382861fe..5db35433c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java @@ -78,9 +78,9 @@ public class TabCompleteRequest implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.compareTo(MINECRAFT_1_13) >= 0) { this.transactionId = ProtocolUtils.readVarInt(buf); - this.command = ProtocolUtils.readString(buf); + this.command = ProtocolUtils.readString(buf, Chat.MAX_SERVERBOUND_MESSAGE_LENGTH); } else { - this.command = ProtocolUtils.readString(buf); + this.command = ProtocolUtils.readString(buf, Chat.MAX_SERVERBOUND_MESSAGE_LENGTH); if (version.compareTo(MINECRAFT_1_9) >= 0) { this.assumeCommand = buf.readBoolean(); } From 91e9769c5aab7a629c1cc7f2a3bc3f6f88ba8582 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 17 May 2020 00:24:00 -0400 Subject: [PATCH 24/24] Cap /server list to 50 servers. Any more, and the server list is arguably useless. Thanks to BXBW for finding this particular issue! --- .../velocitypowered/proxy/command/ServerCommand.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java index b79d955bf..1e21e0ab7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -12,8 +12,6 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; -import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -25,6 +23,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; public class ServerCommand implements Command { + public static final int MAX_SERVERS_TO_LIST = 50; private final ProxyServer server; public ServerCommand(ProxyServer server) { @@ -61,10 +60,16 @@ public class ServerCommand implements Command { executor.sendMessage(of("You are currently connected to " + currentServer + ".", TextColor.YELLOW)); + List servers = BuiltinCommandUtil.sortedServerList(server); + if (servers.size() > MAX_SERVERS_TO_LIST) { + executor.sendMessage(of("Too many servers to list. Tab-complete to show all servers.", + TextColor.RED)); + return; + } + // Assemble the list of servers as components TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ") .color(TextColor.YELLOW); - List servers = BuiltinCommandUtil.sortedServerList(server); for (int i = 0; i < servers.size(); i++) { RegisteredServer rs = servers.get(i); serverListBuilder.append(formatServerComponent(currentServer, rs));