From 9d5930d96b6baca44ee6c7177009d6731966ae06 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 26 Sep 2019 01:04:38 -0400 Subject: [PATCH 01/12] Integrate BungeeQuack functionality into Velocity --- .../backend/BackendPlaySessionHandler.java | 8 + .../backend/BungeeCordMessageResponder.java | 325 ++++++++++++++++++ .../backend/VelocityServerConnection.java | 7 +- .../connection/client/ConnectedPlayer.java | 8 + .../proxy/protocol/util/ByteBufDataInput.java | 110 ++++++ .../protocol/util/ByteBufDataOutput.java | 105 ++++++ .../server/VelocityRegisteredServer.java | 10 +- 7 files changed, 571 insertions(+), 2 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ByteBufDataInput.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ByteBufDataOutput.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index ba6678b78..dbedce09a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -31,6 +31,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { private final VelocityServerConnection serverConn; private final ClientPlaySessionHandler playerSessionHandler; private final MinecraftConnection playerConnection; + private final BungeeCordMessageResponder bungeecordMessageResponder; private boolean exceptionTriggered = false; BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) { @@ -44,6 +45,9 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { "Initializing BackendPlaySessionHandler with no backing client play session handler!"); } this.playerSessionHandler = (ClientPlaySessionHandler) psh; + + this.bungeecordMessageResponder = new BungeeCordMessageResponder(server, + serverConn.getPlayer()); } @Override @@ -86,6 +90,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public boolean handle(PluginMessage packet) { + if (bungeecordMessageResponder.process(packet)) { + return true; + } + if (!serverConn.getPlayer().canForwardPluginMessage(serverConn.ensureConnected() .getProtocolVersion(), packet)) { return true; 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 new file mode 100644 index 000000000..45c697628 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java @@ -0,0 +1,325 @@ +package com.velocitypowered.proxy.connection.backend; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.util.UuidUtils; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.util.ByteBufDataInput; +import com.velocitypowered.proxy.protocol.util.ByteBufDataOutput; +import com.velocitypowered.proxy.server.VelocityRegisteredServer; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; + +class BungeeCordMessageResponder { + + private static final MinecraftChannelIdentifier MODERN_CHANNEL = MinecraftChannelIdentifier + .create("bungeecord", "main"); + private static final LegacyChannelIdentifier LEGACY_CHANNEL = + new LegacyChannelIdentifier("BungeeCord"); + + private final VelocityServer proxy; + private final ConnectedPlayer player; + + BungeeCordMessageResponder(VelocityServer proxy, ConnectedPlayer player) { + this.proxy = proxy; + this.player = player; + } + + private void processConnect(ByteBufDataInput in) { + String serverName = in.readUTF(); + proxy.getServer(serverName).ifPresent(server -> player.createConnectionRequest(server) + .fireAndForget()); + } + + private void processConnectOther(ByteBufDataInput in) { + String playerName = in.readUTF(); + String serverName = in.readUTF(); + + proxy.getPlayer(playerName).ifPresent(player -> { + proxy.getServer(serverName).ifPresent(server -> player.createConnectionRequest(server) + .fireAndForget()); + }); + } + + private void processIp(ByteBufDataInput in) { + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput out = new ByteBufDataOutput(buf); + out.writeUTF("IP"); + out.writeUTF(player.getRemoteAddress().getHostString()); + out.writeInt(player.getRemoteAddress().getPort()); + sendResponse(buf); + } + + private void processPlayerCount(ByteBufDataInput in) { + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput out = new ByteBufDataOutput(buf); + + String target = in.readUTF(); + if (target.equals("ALL")) { + out.writeUTF("PlayerCount"); + out.writeUTF("ALL"); + out.writeInt(proxy.getPlayerCount()); + } else { + proxy.getServer(target).ifPresent(rs -> { + int playersOnServer = rs.getPlayersConnected().size(); + out.writeUTF("PlayerCount"); + out.writeUTF(rs.getServerInfo().getName()); + out.writeInt(playersOnServer); + }); + } + + if (buf.isReadable()) { + sendResponse(buf); + } else { + buf.release(); + } + } + + private void processPlayerList(ByteBufDataInput in) { + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput out = new ByteBufDataOutput(buf); + + String target = in.readUTF(); + if (target.equals("ALL")) { + out.writeUTF("PlayerList"); + out.writeUTF("ALL"); + + StringJoiner joiner = new StringJoiner(", "); + for (Player online : proxy.getAllPlayers()) { + joiner.add(online.getUsername()); + } + out.writeUTF(joiner.toString()); + } else { + proxy.getServer(target).ifPresent(info -> { + out.writeUTF("PlayerList"); + out.writeUTF(info.getServerInfo().getName()); + + StringJoiner joiner = new StringJoiner(", "); + for (Player online : info.getPlayersConnected()) { + joiner.add(online.getUsername()); + } + out.writeUTF(joiner.toString()); + }); + } + + if (buf.isReadable()) { + sendResponse(buf); + } else { + buf.release(); + } + } + + private void processGetServers(ByteBufDataInput in) { + StringJoiner joiner = new StringJoiner(", "); + for (RegisteredServer server : proxy.getAllServers()) { + joiner.add(server.getServerInfo().getName()); + } + + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput out = new ByteBufDataOutput(buf); + out.writeUTF("GetServers"); + out.writeUTF(joiner.toString()); + + sendResponse(buf); + } + + private void processMessage(ByteBufDataInput in) { + String target = in.readUTF(); + String message = in.readUTF(); + if (target.equals("ALL")) { + for (Player player : proxy.getAllPlayers()) { + player.sendMessage(LegacyComponentSerializer.INSTANCE.deserialize(message)); + } + } else { + proxy.getPlayer(target).ifPresent(player -> { + player.sendMessage(LegacyComponentSerializer.INSTANCE.deserialize(message)); + }); + } + } + + private void processGetServer(ByteBufDataInput in) { + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput out = new ByteBufDataOutput(buf); + + out.writeUTF("GetServer"); + out.writeUTF(player.ensureAndGetCurrentServer().getServerInfo().getName()); + + sendResponse(buf); + } + + private void processUuid(ByteBufDataInput in) { + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput out = new ByteBufDataOutput(buf); + + out.writeUTF("UUID"); + out.writeUTF(UuidUtils.toUndashed(player.getUniqueId())); + + sendResponse(buf); + } + + private void processUuidOther(ByteBufDataInput in) { + proxy.getPlayer(in.readUTF()).ifPresent(player -> { + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput out = new ByteBufDataOutput(buf); + + out.writeUTF("UUIDOther"); + out.writeUTF(player.getUsername()); + out.writeUTF(UuidUtils.toUndashed(player.getUniqueId())); + + sendResponse(buf); + }); + } + + private void processServerIp(ByteBufDataInput in) { + proxy.getServer(in.readUTF()).ifPresent(info -> { + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput out = new ByteBufDataOutput(buf); + + out.writeUTF("ServerIP"); + out.writeUTF(info.getServerInfo().getName()); + out.writeUTF(info.getServerInfo().getAddress().getHostString()); + out.writeShort(info.getServerInfo().getAddress().getPort()); + + sendResponse(buf); + }); + } + + private void processKick(ByteBufDataInput in) { + proxy.getPlayer(in.readUTF()).ifPresent(player -> { + String kickReason = in.readUTF(); + player.disconnect(LegacyComponentSerializer.INSTANCE.deserialize(kickReason)); + }); + } + + private ByteBuf prepareForwardMessage(ByteBufDataInput in) { + String channel = in.readUTF(); + short messageLength = in.readShort(); + + ByteBuf buf = Unpooled.buffer(); + ByteBufDataOutput forwarded = new ByteBufDataOutput(buf); + forwarded.writeUTF(channel); + forwarded.writeShort(messageLength); + buf.writeBytes(in.unwrap().readSlice(messageLength)); + return buf; + } + + private void processForwardToPlayer(ByteBufDataInput in) { + proxy.getPlayer(in.readUTF()) + .flatMap(Player::getCurrentServer) + .ifPresent(server -> sendResponse(player, prepareForwardMessage(in))); + } + + private void processForwardToServer(ByteBufDataInput in) { + ByteBuf toForward = prepareForwardMessage(in); + String target = in.readUTF(); + if (target.equals("ALL")) { + ByteBuf unreleasableForward = Unpooled.unreleasableBuffer(toForward); + try { + for (RegisteredServer rs : proxy.getAllServers()) { + ((VelocityRegisteredServer) rs).sendPluginMessage(LEGACY_CHANNEL, unreleasableForward); + } + } finally { + toForward.release(); + } + } else { + proxy.getServer(target).ifPresent(rs -> ((VelocityRegisteredServer) rs) + .sendPluginMessage(LEGACY_CHANNEL, toForward)); + } + } + + // Note: this method will always release the buffer! + private void sendResponse(ByteBuf buf) { + sendResponse(this.player, buf); + } + + // Note: this method will always release the buffer! + private static void sendResponse(ConnectedPlayer player, ByteBuf buf) { + MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected(); + String chan = serverConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) + >= 0 ? MODERN_CHANNEL.getId() : LEGACY_CHANNEL.getId(); + + PluginMessage msg = null; + boolean released = false; + + try { + VelocityServerConnection vsc = player.getConnectedServer(); + if (vsc == null) { + return; + } + + MinecraftConnection serverConn = vsc.ensureConnected(); + msg = new PluginMessage(chan, buf); + serverConn.write(msg); + released = true; + } finally { + if (!released && msg != null) { + msg.release(); + } + } + } + + public boolean process(PluginMessage message) { + if (!MODERN_CHANNEL.getId().equals(message.getChannel()) && !LEGACY_CHANNEL.getId() + .equals(message.getChannel())) { + return false; + } + + ByteBufDataInput in = new ByteBufDataInput(message.content()); + String subChannel = in.readUTF(); + if (subChannel.equals("ForwardToPlayer")) { + this.processForwardToPlayer(in); + } + if (subChannel.equals("Forward")) { + this.processForwardToServer(in); + } + if (subChannel.equals("Connect")) { + this.processConnect(in); + } + if (subChannel.equals("ConnectOther")) { + this.processConnectOther(in); + } + if (subChannel.equals("IP")) { + this.processIp(in); + } + if (subChannel.equals("PlayerCount")) { + this.processPlayerCount(in); + } + if (subChannel.equals("PlayerList")) { + this.processPlayerList(in); + } + if (subChannel.equals("GetServers")) { + this.processGetServers(in); + } + if (subChannel.equals("Message")) { + this.processMessage(in); + } + if (subChannel.equals("GetServer")) { + this.processGetServer(in); + } + if (subChannel.equals("UUID")) { + this.processUuid(in); + } + if (subChannel.equals("UUIDOther")) { + this.processUuidOther(in); + } + if (subChannel.equals("ServerIP")) { + this.processServerIp(in); + } + if (subChannel.equals("KickPlayer")) { + this.processKick(in); + } + + return true; + } +} 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 da9806e16..9f0903469 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 @@ -34,6 +34,7 @@ import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.server.VelocityRegisteredServer; +import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; @@ -210,12 +211,16 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, @Override public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + return sendPluginMessage(identifier, Unpooled.wrappedBuffer(data)); + } + + public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) { Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(data, "data"); MinecraftConnection mc = ensureConnected(); - PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data)); + PluginMessage message = new PluginMessage(identifier.getId(), data); mc.write(message); return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index c012e8fcb..0483ce352 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -135,6 +135,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return Optional.ofNullable(connectedServer); } + public VelocityServerConnection ensureAndGetCurrentServer() { + VelocityServerConnection con = this.connectedServer; + if (con == null) { + throw new IllegalStateException("Not connected to server!"); + } + return con; + } + @Override public GameProfile getGameProfile() { return profile; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ByteBufDataInput.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ByteBufDataInput.java new file mode 100644 index 000000000..43b6d44aa --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ByteBufDataInput.java @@ -0,0 +1,110 @@ +package com.velocitypowered.proxy.protocol.util; + +import com.google.common.io.ByteArrayDataInput; +import io.netty.buffer.ByteBuf; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A wrapper around {@link io.netty.buffer.ByteBuf} that implements the exception-free + * {@link ByteArrayDataInput} interface from Guava. + */ +public class ByteBufDataInput implements ByteArrayDataInput, DataInput { + + private final ByteBuf in; + + /** + * Creates a new ByteBufDataInput instance. The ByteBufDataInput simply "borrows" the ByteBuf + * while it is in use. + * + * @param buf the buffer to read from + */ + public ByteBufDataInput(ByteBuf buf) { + this.in = buf; + } + + public ByteBuf unwrap() { + return in; + } + + @Override + public void readFully(byte[] b) { + in.readBytes(b); + } + + @Override + public void readFully(byte[] b, int off, int len) { + in.readBytes(b, off, len); + } + + @Override + public int skipBytes(int n) { + in.skipBytes(n); + return n; + } + + @Override + public boolean readBoolean() { + return in.readBoolean(); + } + + @Override + public byte readByte() { + return in.readByte(); + } + + @Override + public int readUnsignedByte() { + return in.readUnsignedByte() & 0xFF; + } + + @Override + public short readShort() { + return in.readShort(); + } + + @Override + public int readUnsignedShort() { + return in.readUnsignedShort(); + } + + @Override + public char readChar() { + return in.readChar(); + } + + @Override + public int readInt() { + return in.readInt(); + } + + @Override + public long readLong() { + return in.readLong(); + } + + @Override + public float readFloat() { + return in.readFloat(); + } + + @Override + public double readDouble() { + return in.readDouble(); + } + + @Override + public String readLine() { + throw new UnsupportedOperationException(); + } + + @Override + public String readUTF() { + try { + return DataInputStream.readUTF(this); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ByteBufDataOutput.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ByteBufDataOutput.java new file mode 100644 index 000000000..b894525ab --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ByteBufDataOutput.java @@ -0,0 +1,105 @@ +package com.velocitypowered.proxy.protocol.util; + +import com.google.common.io.ByteArrayDataOutput; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +/** + * A {@link DataOutput} equivalent to {@link ByteBufDataInput}. + */ +public class ByteBufDataOutput extends OutputStream implements DataOutput, ByteArrayDataOutput { + + private final ByteBuf buf; + private final DataOutputStream utf8out; + + public ByteBufDataOutput(ByteBuf buf) { + this.buf = buf; + this.utf8out = new DataOutputStream(this); + } + + @Override + public byte[] toByteArray() { + return ByteBufUtil.getBytes(buf); + } + + @Override + public void write(int b) { + buf.writeByte(b); + } + + @Override + public void write(byte[] b) { + buf.writeBytes(b); + } + + @Override + public void write(byte[] b, int off, int len) { + buf.writeBytes(b, off, len); + } + + @Override + public void writeBoolean(boolean v) { + buf.writeBoolean(v); + } + + @Override + public void writeByte(int v) { + buf.writeByte(v); + } + + @Override + public void writeShort(int v) { + buf.writeShort(v); + } + + @Override + public void writeChar(int v) { + buf.writeChar(v); + } + + @Override + public void writeInt(int v) { + buf.writeInt(v); + } + + @Override + public void writeLong(long v) { + buf.writeLong(v); + } + + @Override + public void writeFloat(float v) { + buf.writeFloat(v); + } + + @Override + public void writeDouble(double v) { + buf.writeDouble(v); + } + + @Override + public void writeBytes(String s) { + buf.writeCharSequence(s, StandardCharsets.US_ASCII); + } + + @Override + public void writeChars(String s) { + for (char c : s.toCharArray()) { + buf.writeChar(c); + } + } + + @Override + public void writeUTF(String s) { + try { + this.utf8out.writeUTF(s); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java index cf6694cb4..e999d5e7c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -18,12 +18,16 @@ import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; 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 com.velocitypowered.proxy.protocol.packet.PluginMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -119,8 +123,12 @@ public class VelocityRegisteredServer implements RegisteredServer { @Override public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + return sendPluginMessage(identifier, Unpooled.wrappedBuffer(data)); + } + + public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) { for (ConnectedPlayer player : players) { - ServerConnection connection = player.getConnectedServer(); + VelocityServerConnection connection = player.getConnectedServer(); if (connection != null && connection.getServerInfo().equals(serverInfo)) { return connection.sendPluginMessage(identifier, data); } From 010a10f50f413bc86e8ac9533166515e125c5074 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 15 Nov 2019 01:35:44 -0500 Subject: [PATCH 02/12] Do not try to prevent people from running Velocity behind Velocity. This commit absolutely does not change our support policy on this: this is a completely unsupported setup. In any event, there is an existing forwarding check in Velocity that covers this case quite well. I am making this change to make the login process less "chatty" for higher-latency links and 1.13+ servers. --- .../client/LoginSessionHandler.java | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) 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 05b032ce6..b2c47847f 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 @@ -66,7 +66,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler { private final InitialInboundConnection inbound; private @MonotonicNonNull ServerLogin login; private byte[] verify = EMPTY_BYTE_ARRAY; - private int playerInfoId; private @MonotonicNonNull ConnectedPlayer connectedPlayer; LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection, @@ -79,29 +78,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(ServerLogin packet) { this.login = packet; - if (mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0) { - // To make sure the connecting client isn't Velocity, send a plugin message that Velocity will - // recognize and respond to. - playerInfoId = ThreadLocalRandom.current().nextInt(); - mcConnection.write(new LoginPluginMessage(playerInfoId, VELOCITY_IP_FORWARDING_CHANNEL, - Unpooled.EMPTY_BUFFER)); - } else { - beginPreLogin(); - } - return true; - } - - @Override - public boolean handle(LoginPluginResponse packet) { - if (packet.getId() == playerInfoId) { - if (packet.isSuccess()) { - // Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening. - inbound.disconnect(VelocityMessages.NO_PROXY_BEHIND_PROXY); - } else { - // Proceed with the regular login process. - beginPreLogin(); - } - } + beginPreLogin(); return true; } From e12f970684c09ffc1d4f29a4f5c5825823922456 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 16 Nov 2019 22:34:30 -0500 Subject: [PATCH 03/12] Fix fresh 1.1.0 velocity.toml generation. --- .../com/velocitypowered/proxy/config/VelocityConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6d8c24f4d..632a0a549 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -100,7 +100,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi " configuration is used if no servers could be contacted." }) @ConfigKey("ping-passthrough") - private PingPassthroughMode pingPassthrough; + private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; @Table("[servers]") private final Servers servers; From d2b8271eb4b23578d833058af4e2fb1cbff4ba94 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 16 Nov 2019 23:17:09 -0500 Subject: [PATCH 04/12] Allow running Velocity without any servers. This is a niche setup, however if your network is 100% dynamically configured, this is a handy feature to have available. To support this functionality, a new PlayerChooseInitialServerEvent event was added to allow the initial server to connect to be changed as desired. --- .../PlayerChooseInitialServerEvent.java | 51 ++++++++++++++++++ .../proxy/config/VelocityConfiguration.java | 54 +++++++++---------- .../connection/client/ConnectedPlayer.java | 37 +++++-------- .../client/LoginSessionHandler.java | 35 ++++++------ 4 files changed, 106 insertions(+), 71 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/PlayerChooseInitialServerEvent.java diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerChooseInitialServerEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChooseInitialServerEvent.java new file mode 100644 index 000000000..3ff289363 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChooseInitialServerEvent.java @@ -0,0 +1,51 @@ +package com.velocitypowered.api.event.player; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import java.util.Optional; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Fired when a player has finished connecting to the proxy and we need to choose the first server + * to connect to. + */ +public class PlayerChooseInitialServerEvent { + + private final Player player; + private @Nullable RegisteredServer initialServer; + + /** + * Constructs a PlayerChooseInitialServerEvent. + * @param player the player that was connected + * @param initialServer the initial server selected, may be {@code null} + */ + public PlayerChooseInitialServerEvent(Player player, @Nullable RegisteredServer initialServer) { + this.player = Preconditions.checkNotNull(player, "player"); + this.initialServer = initialServer; + } + + public Player getPlayer() { + return player; + } + + public Optional getInitialServer() { + return Optional.ofNullable(initialServer); + } + + /** + * Sets the new initial server. + * @param server the initial server the player should connect to + */ + public void setInitialServer(RegisteredServer server) { + this.initialServer = server; + } + + @Override + public String toString() { + return "PlayerChooseInitialServerEvent{" + + "player=" + player + + ", initialServer=" + initialServer + + '}'; + } +} 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 632a0a549..467363daf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -192,44 +192,38 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi } if (servers.getServers().isEmpty()) { - logger.error("You have no servers configured. :("); - valid = false; - } else { - if (servers.getAttemptConnectionOrder().isEmpty()) { - logger.error("No fallback servers are configured!"); + logger.warn("You don't have any servers configured."); + } + + for (Map.Entry entry : servers.getServers().entrySet()) { + try { + AddressUtil.parseAddress(entry.getValue()); + } catch (IllegalArgumentException e) { + logger.error("Server {} does not have a valid IP address.", entry.getKey(), e); valid = false; } + } - for (Map.Entry entry : servers.getServers().entrySet()) { - try { - AddressUtil.parseAddress(entry.getValue()); - } catch (IllegalArgumentException e) { - logger.error("Server {} does not have a valid IP address.", entry.getKey(), e); - valid = false; - } + for (String s : servers.getAttemptConnectionOrder()) { + if (!servers.getServers().containsKey(s)) { + logger.error("Fallback server " + s + " is not registered in your configuration!"); + valid = false; + } + } + + for (Map.Entry> entry : forcedHosts.getForcedHosts().entrySet()) { + if (entry.getValue().isEmpty()) { + logger.error("Forced host '{}' does not contain any servers", entry.getKey()); + valid = false; + continue; } - for (String s : servers.getAttemptConnectionOrder()) { - if (!servers.getServers().containsKey(s)) { - logger.error("Fallback server " + s + " is not registered in your configuration!"); + for (String server : entry.getValue()) { + if (!servers.getServers().containsKey(server)) { + logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey()); valid = false; } } - - for (Map.Entry> entry : forcedHosts.getForcedHosts().entrySet()) { - if (entry.getValue().isEmpty()) { - logger.error("Forced host '{}' does not contain any servers", entry.getKey()); - valid = false; - continue; - } - - for (String server : entry.getValue()) { - if (!servers.getServers().containsKey(server)) { - logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey()); - valid = false; - } - } - } } try { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index e7be50b36..1145e905b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -417,33 +417,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return; } - if (connectedServer == null) { - Optional nextServer = getNextServerToTry(rs); - if (nextServer.isPresent()) { - // There can't be any connection in flight now. - resetInFlightConnection(); - createConnectionRequest(nextServer.get()).fireAndForget(); - } else { - disconnect(friendlyReason); - } + boolean kickedFromCurrent = connectedServer == null || connectedServer.getServer().equals(rs); + ServerKickResult result; + if (kickedFromCurrent) { + Optional next = getNextServerToTry(rs); + result = next.map(RedirectPlayer::create) + .orElseGet(() -> DisconnectPlayer.create(friendlyReason)); } else { - boolean kickedFromCurrent = connectedServer.getServer().equals(rs); - ServerKickResult result; - if (kickedFromCurrent) { - Optional next = getNextServerToTry(rs); - result = next.map(RedirectPlayer::create) - .orElseGet(() -> DisconnectPlayer.create(friendlyReason)); - } else { - // If we were kicked by going to another server, the connection should not be in flight - if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) { - resetInFlightConnection(); - } - result = Notify.create(friendlyReason); + // If we were kicked by going to another server, the connection should not be in flight + if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) { + resetInFlightConnection(); } - KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason, - !kickedFromCurrent, result); - handleKickEvent(originalEvent, friendlyReason); + result = Notify.create(friendlyReason); } + KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason, + !kickedFromCurrent, result); + handleKickEvent(originalEvent, friendlyReason); } private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) { 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 b2c47847f..6fb58ffea 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 @@ -1,24 +1,21 @@ package com.velocitypowered.proxy.connection.client; import static com.google.common.net.UrlEscapers.urlFormParameterEscaper; -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.VelocityServer.GSON; import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; -import static com.velocitypowered.proxy.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL; import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa; import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId; import static org.asynchttpclient.Dsl.asyncHttpClient; -import static org.asynchttpclient.Dsl.config; import com.google.common.base.Preconditions; -import com.google.common.net.UrlEscapers; import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; +import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.VelocityServer; @@ -29,17 +26,12 @@ import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; -import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; -import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.SetCompression; import com.velocitypowered.proxy.util.VelocityMessages; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Arrays; @@ -50,7 +42,6 @@ import java.util.concurrent.ThreadLocalRandom; import net.kyori.text.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.asynchttpclient.Dsl; import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.Response; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -231,12 +222,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } private void finishLogin(ConnectedPlayer player) { - Optional toTry = player.getNextServerToTry(); - if (!toTry.isPresent()) { - player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS); - return; - } - int threshold = server.getConfiguration().getCompressionThreshold(); if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { mcConnection.write(new SetCompression(threshold)); @@ -269,11 +254,27 @@ public class LoginSessionHandler implements MinecraftSessionHandler { mcConnection.setSessionHandler(new InitialConnectSessionHandler(player)); server.getEventManager().fire(new PostLoginEvent(player)) - .thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget()); + .thenRun(() -> connectToInitialServer(player)); } }, mcConnection.eventLoop()); } + private void connectToInitialServer(ConnectedPlayer player) { + Optional initialFromConfig = player.getNextServerToTry(); + PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player, + initialFromConfig.orElse(null)); + + server.getEventManager().fire(event) + .thenRunAsync(() -> { + Optional toTry = event.getInitialServer(); + if (!toTry.isPresent()) { + player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS); + return; + } + player.createConnectionRequest(toTry.get()).fireAndForget(); + }, mcConnection.eventLoop()); + } + @Override public void handleUnknown(ByteBuf buf) { mcConnection.close(); From ae14eb8ccb56f6b12e210a145837f4545607f416 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 23 Nov 2019 01:06:00 -0500 Subject: [PATCH 05/12] Fix potential UDP speculative reflection attack --- .../proxy/protocol/netty/GS4QueryHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java index 8bcda72de..b3407c980 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java @@ -18,6 +18,7 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.DatagramPacket; import java.net.InetAddress; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -59,6 +60,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler private final Cache sessions = CacheBuilder.newBuilder() .expireAfterWrite(30, TimeUnit.SECONDS) .build(); + private final SecureRandom random; private volatile @MonotonicNonNull List pluginInformationList = null; @@ -67,6 +69,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler public GS4QueryHandler(VelocityServer server) { this.server = server; + this.random = new SecureRandom(); } private QueryResponse createInitialResponse() { @@ -111,7 +114,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler switch (type) { case QUERY_TYPE_HANDSHAKE: { // Generate new challenge token and put it into the sessions cache - int challengeToken = ThreadLocalRandom.current().nextInt(); + int challengeToken = random.nextInt(); sessions.put(senderAddress, challengeToken); // Respond with challenge token From e29e20b1e82cee467bf45f92780570f3e9d6dada Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 26 Nov 2019 13:44:21 -0500 Subject: [PATCH 06/12] Fix 1.13+ tab-complete with command but no arguments As a nice side-effect, this simplifies the tab complete code a bit. --- .../client/ClientPlaySessionHandler.java | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 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 9b22408b4..a1ae14faf 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 @@ -386,31 +386,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } List offers = new ArrayList<>(); - int longestLength = 0; for (String suggestion : suggestions) { offers.add(new Offer(suggestion)); - if (suggestion.length() > longestLength) { - longestLength = suggestion.length(); - } } - TabCompleteResponse resp = new TabCompleteResponse(); - resp.setTransactionId(packet.getTransactionId()); - int startPos = packet.getCommand().lastIndexOf(' ') + 1; - int length; - if (startPos == 0) { - startPos = packet.getCommand().length() + 1; - length = longestLength; - } else { - length = packet.getCommand().length() - startPos; + if (startPos > 0) { + TabCompleteResponse resp = new TabCompleteResponse(); + resp.setTransactionId(packet.getTransactionId()); + resp.setStart(startPos); + resp.setLength(packet.getCommand().length() - startPos); + resp.getOffers().addAll(offers); + player.getConnection().write(resp); } - - resp.setStart(startPos); - resp.setLength(length); - resp.getOffers().addAll(offers); - - player.getConnection().write(resp); return true; } From bfeca40b1701c0aab15da66d9a325735ef1b9465 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 26 Nov 2019 14:07:55 -0500 Subject: [PATCH 07/12] Make sure we only tab-complete commands for which we have access to --- .../com/velocitypowered/proxy/command/VelocityCommand.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 a67582a0a..1c3b777c3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -72,7 +72,10 @@ public class VelocityCommand implements Command { @Override public List suggest(CommandSource source, String @NonNull [] currentArgs) { if (currentArgs.length == 0) { - return ImmutableList.copyOf(subcommands.keySet()); + return subcommands.entrySet().stream() + .filter(e -> e.getValue().hasPermission(source, new String[0])) + .map(Map.Entry::getKey) + .collect(ImmutableList.toImmutableList()); } if (currentArgs.length == 1) { @@ -81,7 +84,7 @@ public class VelocityCommand implements Command { currentArgs[0].length())) .filter(e -> e.getValue().hasPermission(source, new String[0])) .map(Map.Entry::getKey) - .collect(Collectors.toList()); + .collect(ImmutableList.toImmutableList()); } Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US)); From 0b6fc06e6724db5d4de79edb65c72fa55189a80c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 26 Nov 2019 14:37:44 -0500 Subject: [PATCH 08/12] Initial 1.15-pre2 support --- .../api/network/ProtocolVersion.java | 3 +- .../client/ClientPlaySessionHandler.java | 4 +- .../proxy/protocol/StateRegistry.java | 40 +++++++++++++------ .../proxy/protocol/packet/JoinGame.java | 19 +++++++++ .../proxy/protocol/packet/Respawn.java | 20 +++++++++- 5 files changed, 69 insertions(+), 17 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index 93b1a3720..5c7d849a8 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -33,7 +33,8 @@ public enum ProtocolVersion { MINECRAFT_1_14_1(480, "1.14.1"), MINECRAFT_1_14_2(485, "1.14.2"), MINECRAFT_1_14_3(490, "1.14.3"), - MINECRAFT_1_14_4(498, "1.14.4"); + MINECRAFT_1_14_4(498, "1.14.4"), + MINECRAFT_1_15(566, "1.15-pre2"); private final int protocol; private final String name; 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 a1ae14faf..a519436d1 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 @@ -316,8 +316,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { joinGame.setDimension(getFakeTemporaryDimensionId(realDim)); player.getConnection().delayedWrite(joinGame); player.getConnection().delayedWrite( - new Respawn(realDim, joinGame.getDifficulty(), joinGame.getGamemode(), - joinGame.getLevelType())); + new Respawn(realDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), + joinGame.getGamemode(), joinGame.getLevelType())); } // Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 4444722cb..efe241454 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -5,6 +5,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_15; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; @@ -120,51 +121,61 @@ public enum StateRegistry { map(0x1F, MINECRAFT_1_14, false)); clientbound.register(BossBar.class, BossBar::new, - map(0x0C, MINECRAFT_1_9, false)); + map(0x0C, MINECRAFT_1_9, false), + map(0x0D, MINECRAFT_1_15, false)); clientbound.register(Chat.class, Chat::new, map(0x02, MINECRAFT_1_7_2, true), map(0x0F, MINECRAFT_1_9, true), - map(0x0E, MINECRAFT_1_13, true)); + map(0x0E, MINECRAFT_1_13, true), + map(0x0F, MINECRAFT_1_15, true)); clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new, map(0x3A, MINECRAFT_1_7_2, false), map(0x0E, MINECRAFT_1_9, false), - map(0x10, MINECRAFT_1_13, false)); + map(0x10, MINECRAFT_1_13, false), + map(0x11, MINECRAFT_1_15, false)); clientbound.register(AvailableCommands.class, AvailableCommands::new, - map(0x11, MINECRAFT_1_13, false)); + map(0x11, MINECRAFT_1_13, false), + map(0x12, MINECRAFT_1_15, false)); clientbound.register(PluginMessage.class, PluginMessage::new, map(0x3F, MINECRAFT_1_7_2, false), map(0x18, MINECRAFT_1_9, false), map(0x19, MINECRAFT_1_13, false), - map(0x18, MINECRAFT_1_14, false)); + map(0x18, MINECRAFT_1_14, false), + map(0x19, MINECRAFT_1_15, false)); clientbound.register(Disconnect.class, Disconnect::new, map(0x40, MINECRAFT_1_7_2, false), map(0x1A, MINECRAFT_1_9, false), map(0x1B, MINECRAFT_1_13, false), - map(0x1A, MINECRAFT_1_14, false)); + map(0x1A, MINECRAFT_1_14, false), + map(0x1B, MINECRAFT_1_15, false)); clientbound.register(KeepAlive.class, KeepAlive::new, map(0x00, MINECRAFT_1_7_2, false), map(0x1F, MINECRAFT_1_9, false), map(0x21, MINECRAFT_1_13, false), - map(0x20, MINECRAFT_1_14, false)); + map(0x20, MINECRAFT_1_14, false), + map(0x21, MINECRAFT_1_15, false)); clientbound.register(JoinGame.class, JoinGame::new, map(0x01, MINECRAFT_1_7_2, false), map(0x23, MINECRAFT_1_9, false), map(0x25, MINECRAFT_1_13, false), - map(0x25, MINECRAFT_1_14, false)); + map(0x25, MINECRAFT_1_14, false), + map(0x26, MINECRAFT_1_15, false)); clientbound.register(Respawn.class, Respawn::new, map(0x07, MINECRAFT_1_7_2, true), map(0x33, MINECRAFT_1_9, true), map(0x34, MINECRAFT_1_12, true), map(0x35, MINECRAFT_1_12_1, true), map(0x38, MINECRAFT_1_13, true), - map(0x3A, MINECRAFT_1_14, true)); + map(0x3A, MINECRAFT_1_14, true), + map(0x3B, MINECRAFT_1_15, true)); clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, map(0x48, MINECRAFT_1_8, true), map(0x32, MINECRAFT_1_9, true), map(0x33, MINECRAFT_1_12, true), map(0x34, MINECRAFT_1_12_1, true), map(0x37, MINECRAFT_1_13, true), - map(0x39, MINECRAFT_1_14, true)); + map(0x39, MINECRAFT_1_14, true), + map(0x3A, MINECRAFT_1_15, true)); clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new, map(0x47, MINECRAFT_1_8, true), map(0x48, MINECRAFT_1_9, true), @@ -172,20 +183,23 @@ public enum StateRegistry { map(0x49, MINECRAFT_1_12, true), map(0x4A, MINECRAFT_1_12_1, true), map(0x4E, MINECRAFT_1_13, true), - map(0x53, MINECRAFT_1_14, true)); + map(0x53, MINECRAFT_1_14, true), + map(0x54, MINECRAFT_1_15, true)); clientbound.register(TitlePacket.class, TitlePacket::new, map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_9, true), map(0x47, MINECRAFT_1_12, true), map(0x48, MINECRAFT_1_12_1, true), map(0x4B, MINECRAFT_1_13, true), - map(0x4F, MINECRAFT_1_14, true)); + map(0x4F, MINECRAFT_1_14, true), + map(0x50, MINECRAFT_1_15, true)); clientbound.register(PlayerListItem.class, PlayerListItem::new, map(0x38, MINECRAFT_1_7_2, false), map(0x2D, MINECRAFT_1_9, false), map(0x2E, MINECRAFT_1_12_1, false), map(0x30, MINECRAFT_1_13, false), - map(0x33, MINECRAFT_1_14, false)); + map(0x33, MINECRAFT_1_14, false), + map(0x34, MINECRAFT_1_15, false)); } }, LOGIN { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java index 0b542803f..634fd1c4c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java @@ -12,11 +12,13 @@ public class JoinGame implements MinecraftPacket { private int entityId; private short gamemode; private int dimension; + private long partialHashedSeed; // 1.15+ private short difficulty; private short maxPlayers; private @Nullable String levelType; private int viewDistance; //1.14+ private boolean reducedDebugInfo; + private boolean mystery; public int getEntityId() { return entityId; @@ -42,6 +44,10 @@ public class JoinGame implements MinecraftPacket { this.dimension = dimension; } + public long getPartialHashedSeed() { + return partialHashedSeed; + } + public short getDifficulty() { return difficulty; } @@ -91,6 +97,7 @@ public class JoinGame implements MinecraftPacket { + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" + dimension + + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType + '\'' @@ -111,6 +118,9 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { this.difficulty = buf.readUnsignedByte(); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + this.partialHashedSeed = buf.readLong(); + } this.maxPlayers = buf.readUnsignedByte(); this.levelType = ProtocolUtils.readString(buf, 16); if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { @@ -119,6 +129,9 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { this.reducedDebugInfo = buf.readBoolean(); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + this.mystery = buf.readBoolean(); + } } @Override @@ -133,6 +146,9 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { buf.writeByte(difficulty); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + buf.writeLong(partialHashedSeed); + } buf.writeByte(maxPlayers); if (levelType == null) { throw new IllegalStateException("No level type specified."); @@ -144,6 +160,9 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { buf.writeBoolean(reducedDebugInfo); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + buf.writeBoolean(mystery); + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 4979954fe..847a722c0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf; public class Respawn implements MinecraftPacket { private int dimension; + private long partialHashedSeed; private short difficulty; private short gamemode; private String levelType = ""; @@ -16,8 +17,10 @@ public class Respawn implements MinecraftPacket { public Respawn() { } - public Respawn(int dimension, short difficulty, short gamemode, String levelType) { + public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode, + String levelType) { this.dimension = dimension; + this.partialHashedSeed = partialHashedSeed; this.difficulty = difficulty; this.gamemode = gamemode; this.levelType = levelType; @@ -31,6 +34,14 @@ public class Respawn implements MinecraftPacket { this.dimension = dimension; } + public long getPartialHashedSeed() { + return partialHashedSeed; + } + + public void setPartialHashedSeed(long partialHashedSeed) { + this.partialHashedSeed = partialHashedSeed; + } + public short getDifficulty() { return difficulty; } @@ -59,6 +70,7 @@ public class Respawn implements MinecraftPacket { public String toString() { return "Respawn{" + "dimension=" + dimension + + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty + ", gamemode=" + gamemode + ", levelType='" + levelType + '\'' @@ -71,6 +83,9 @@ public class Respawn implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { this.difficulty = buf.readUnsignedByte(); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + this.partialHashedSeed = buf.readLong(); + } this.gamemode = buf.readUnsignedByte(); this.levelType = ProtocolUtils.readString(buf, 16); } @@ -81,6 +96,9 @@ public class Respawn implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { buf.writeByte(difficulty); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + buf.writeLong(partialHashedSeed); + } buf.writeByte(gamemode); ProtocolUtils.writeString(buf, levelType); } From 87ad188f9261ea52bb5267b1ae60e6eaa25af92f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 26 Nov 2019 16:27:30 -0500 Subject: [PATCH 09/12] Finalize integrated BungeeQuack --- .../backend/BackendPlaySessionHandler.java | 7 ++ .../backend/BungeeCordMessageResponder.java | 112 +++++++++--------- 2 files changed, 65 insertions(+), 54 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index dbedce09a..747006dce 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -1,5 +1,8 @@ package com.velocitypowered.proxy.connection.backend; +import static com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder.getBungeeCordChannel; + +import com.google.common.collect.ImmutableList; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; @@ -53,6 +56,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void activated() { serverConn.getServer().addPlayer(serverConn.getPlayer()); + MinecraftConnection serverMc = serverConn.ensureConnected(); + serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(), + ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion())) + )); } @Override 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 45c697628..018f0ea17 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 @@ -2,7 +2,6 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.proxy.server.RegisteredServer; @@ -15,10 +14,8 @@ import com.velocitypowered.proxy.protocol.util.ByteBufDataInput; import com.velocitypowered.proxy.protocol.util.ByteBufDataOutput; import com.velocitypowered.proxy.server.VelocityRegisteredServer; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import java.util.StringJoiner; -import java.util.stream.Collectors; import net.kyori.text.serializer.legacy.LegacyComponentSerializer; class BungeeCordMessageResponder { @@ -46,10 +43,8 @@ class BungeeCordMessageResponder { String playerName = in.readUTF(); String serverName = in.readUTF(); - proxy.getPlayer(playerName).ifPresent(player -> { - proxy.getServer(serverName).ifPresent(server -> player.createConnectionRequest(server) - .fireAndForget()); - }); + proxy.getPlayer(playerName).flatMap(player -> proxy.getServer(serverName)) + .ifPresent(server -> player.createConnectionRequest(server).fireAndForget()); } private void processIp(ByteBufDataInput in) { @@ -120,7 +115,7 @@ class BungeeCordMessageResponder { } } - private void processGetServers(ByteBufDataInput in) { + private void processGetServers() { StringJoiner joiner = new StringJoiner(", "); for (RegisteredServer server : proxy.getAllServers()) { joiner.add(server.getServerInfo().getName()); @@ -148,7 +143,7 @@ class BungeeCordMessageResponder { } } - private void processGetServer(ByteBufDataInput in) { + private void processGetServer() { ByteBuf buf = Unpooled.buffer(); ByteBufDataOutput out = new ByteBufDataOutput(buf); @@ -158,7 +153,7 @@ class BungeeCordMessageResponder { sendResponse(buf); } - private void processUuid(ByteBufDataInput in) { + private void processUuid() { ByteBuf buf = Unpooled.buffer(); ByteBufDataOutput out = new ByteBufDataOutput(buf); @@ -243,11 +238,15 @@ class BungeeCordMessageResponder { sendResponse(this.player, buf); } + static String getBungeeCordChannel(ProtocolVersion version) { + return version.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0 ? MODERN_CHANNEL.getId() + : LEGACY_CHANNEL.getId(); + } + // Note: this method will always release the buffer! private static void sendResponse(ConnectedPlayer player, ByteBuf buf) { MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected(); - String chan = serverConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) - >= 0 ? MODERN_CHANNEL.getId() : LEGACY_CHANNEL.getId(); + String chan = getBungeeCordChannel(serverConnection.getProtocolVersion()); PluginMessage msg = null; boolean released = false; @@ -269,7 +268,7 @@ class BungeeCordMessageResponder { } } - public boolean process(PluginMessage message) { + boolean process(PluginMessage message) { if (!MODERN_CHANNEL.getId().equals(message.getChannel()) && !LEGACY_CHANNEL.getId() .equals(message.getChannel())) { return false; @@ -277,47 +276,52 @@ class BungeeCordMessageResponder { ByteBufDataInput in = new ByteBufDataInput(message.content()); String subChannel = in.readUTF(); - if (subChannel.equals("ForwardToPlayer")) { - this.processForwardToPlayer(in); - } - if (subChannel.equals("Forward")) { - this.processForwardToServer(in); - } - if (subChannel.equals("Connect")) { - this.processConnect(in); - } - if (subChannel.equals("ConnectOther")) { - this.processConnectOther(in); - } - if (subChannel.equals("IP")) { - this.processIp(in); - } - if (subChannel.equals("PlayerCount")) { - this.processPlayerCount(in); - } - if (subChannel.equals("PlayerList")) { - this.processPlayerList(in); - } - if (subChannel.equals("GetServers")) { - this.processGetServers(in); - } - if (subChannel.equals("Message")) { - this.processMessage(in); - } - if (subChannel.equals("GetServer")) { - this.processGetServer(in); - } - if (subChannel.equals("UUID")) { - this.processUuid(in); - } - if (subChannel.equals("UUIDOther")) { - this.processUuidOther(in); - } - if (subChannel.equals("ServerIP")) { - this.processServerIp(in); - } - if (subChannel.equals("KickPlayer")) { - this.processKick(in); + switch (subChannel) { + case "ForwardToPlayer": + this.processForwardToPlayer(in); + break; + case "Forward": + this.processForwardToServer(in); + break; + case "Connect": + this.processConnect(in); + break; + case "ConnectOther": + this.processConnectOther(in); + break; + case "IP": + this.processIp(in); + break; + case "PlayerCount": + this.processPlayerCount(in); + break; + case "PlayerList": + this.processPlayerList(in); + break; + case "GetServers": + this.processGetServers(); + break; + case "Message": + this.processMessage(in); + break; + case "GetServer": + this.processGetServer(); + break; + case "UUID": + this.processUuid(); + break; + case "UUIDOther": + this.processUuidOther(in); + break; + case "ServerIP": + this.processServerIp(in); + break; + case "KickPlayer": + this.processKick(in); + break; + default: + // Do nothing, unknown command + break; } return true; From 0c14eabe6b1ed9ce4b06fc6e8b06cd18c840d35c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 26 Nov 2019 16:32:50 -0500 Subject: [PATCH 10/12] Fix Checkstyle issues --- .../backend/BungeeCordMessageResponder.java | 24 +++++++++---------- .../backend/VelocityServerConnection.java | 6 +++++ .../connection/client/ConnectedPlayer.java | 4 ++++ .../server/VelocityRegisteredServer.java | 6 +++++ 4 files changed, 28 insertions(+), 12 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 018f0ea17..678c77e8f 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 @@ -53,7 +53,7 @@ class BungeeCordMessageResponder { out.writeUTF("IP"); out.writeUTF(player.getRemoteAddress().getHostString()); out.writeInt(player.getRemoteAddress().getPort()); - sendResponse(buf); + sendResponseOnConnection(buf); } private void processPlayerCount(ByteBufDataInput in) { @@ -75,7 +75,7 @@ class BungeeCordMessageResponder { } if (buf.isReadable()) { - sendResponse(buf); + sendResponseOnConnection(buf); } else { buf.release(); } @@ -109,7 +109,7 @@ class BungeeCordMessageResponder { } if (buf.isReadable()) { - sendResponse(buf); + sendResponseOnConnection(buf); } else { buf.release(); } @@ -126,7 +126,7 @@ class BungeeCordMessageResponder { out.writeUTF("GetServers"); out.writeUTF(joiner.toString()); - sendResponse(buf); + sendResponseOnConnection(buf); } private void processMessage(ByteBufDataInput in) { @@ -150,7 +150,7 @@ class BungeeCordMessageResponder { out.writeUTF("GetServer"); out.writeUTF(player.ensureAndGetCurrentServer().getServerInfo().getName()); - sendResponse(buf); + sendResponseOnConnection(buf); } private void processUuid() { @@ -160,7 +160,7 @@ class BungeeCordMessageResponder { out.writeUTF("UUID"); out.writeUTF(UuidUtils.toUndashed(player.getUniqueId())); - sendResponse(buf); + sendResponseOnConnection(buf); } private void processUuidOther(ByteBufDataInput in) { @@ -172,7 +172,7 @@ class BungeeCordMessageResponder { out.writeUTF(player.getUsername()); out.writeUTF(UuidUtils.toUndashed(player.getUniqueId())); - sendResponse(buf); + sendResponseOnConnection(buf); }); } @@ -186,7 +186,7 @@ class BungeeCordMessageResponder { out.writeUTF(info.getServerInfo().getAddress().getHostString()); out.writeShort(info.getServerInfo().getAddress().getPort()); - sendResponse(buf); + sendResponseOnConnection(buf); }); } @@ -212,7 +212,7 @@ class BungeeCordMessageResponder { private void processForwardToPlayer(ByteBufDataInput in) { proxy.getPlayer(in.readUTF()) .flatMap(Player::getCurrentServer) - .ifPresent(server -> sendResponse(player, prepareForwardMessage(in))); + .ifPresent(server -> sendServerResponse(player, prepareForwardMessage(in))); } private void processForwardToServer(ByteBufDataInput in) { @@ -234,8 +234,8 @@ class BungeeCordMessageResponder { } // Note: this method will always release the buffer! - private void sendResponse(ByteBuf buf) { - sendResponse(this.player, buf); + private void sendResponseOnConnection(ByteBuf buf) { + sendServerResponse(this.player, buf); } static String getBungeeCordChannel(ProtocolVersion version) { @@ -244,7 +244,7 @@ class BungeeCordMessageResponder { } // Note: this method will always release the buffer! - private static void sendResponse(ConnectedPlayer player, ByteBuf buf) { + private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) { MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected(); String chan = getBungeeCordChannel(serverConnection.getProtocolVersion()); 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 9f0903469..a7cf3ed82 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 @@ -214,6 +214,12 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, return sendPluginMessage(identifier, Unpooled.wrappedBuffer(data)); } + /** + * Sends a plugin message to the server through this connection. + * @param identifier the channel ID to use + * @param data the data + * @return whether or not the message was sent + */ public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) { Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(data, "data"); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 97dc93305..d90fc2054 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -135,6 +135,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return Optional.ofNullable(connectedServer); } + /** + * Makes sure the player is connected to a server and returns the server they are connected to. + * @return the server the player is connected to + */ public VelocityServerConnection ensureAndGetCurrentServer() { VelocityServerConnection con = this.connectedServer; if (con == null) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java index e999d5e7c..dc8f1687d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -126,6 +126,12 @@ public class VelocityRegisteredServer implements RegisteredServer { return sendPluginMessage(identifier, Unpooled.wrappedBuffer(data)); } + /** + * Sends a plugin message to the server through this connection. + * @param identifier the channel ID to use + * @param data the data + * @return whether or not the message was sent + */ public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) { for (ConnectedPlayer player : players) { VelocityServerConnection connection = player.getConnectedServer(); From 467fe34da38a7a8b820d7cbfcfcef9301fa97eea Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 27 Nov 2019 01:01:50 -0500 Subject: [PATCH 11/12] Provide useful information for "unexpectedly disconnected from server" Fixes #233 --- .../connection/backend/LoginSessionHandler.java | 17 ++++++++++++++--- .../proxy/util/except/QuietException.java | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index d18951369..5627d9ba5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -17,9 +17,9 @@ import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.SetCompression; +import com.velocitypowered.proxy.util.except.QuietException; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.concurrent.CompletableFuture; @@ -111,8 +111,19 @@ public class LoginSessionHandler implements MinecraftSessionHandler { @Override public void disconnected() { - resultFuture - .completeExceptionally(new IOException("Unexpectedly disconnected from remote server")); + if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { + resultFuture.completeExceptionally( + new QuietException("The connection to the remote server was unexpectedly closed.\n" + + "This is usually because the remote server does not have BungeeCord IP forwarding " + + "correctly enabled.\nSee " + + "https://docs.velocitypowered.com/en/latest/users/player-info-forwarding.html " + + "for instructions on how to configure player info forwarding correctly.") + ); + } else { + resultFuture.completeExceptionally( + new QuietException("The connection to the remote server was unexpectedly closed.") + ); + } } private static ByteBuf createForwardingData(byte[] hmacSecret, String address, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java b/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java new file mode 100644 index 000000000..f095f5779 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java @@ -0,0 +1,17 @@ +package com.velocitypowered.proxy.util.except; + +/** + * A special-purpose exception thrown when we want to indicate an error condition but do not want + * to see a large stack trace in logs. + */ +public class QuietException extends Exception { + + public QuietException(String message) { + super(message); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} From 68e5372b2e682d19068a4a221ce427847714d657 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 27 Nov 2019 01:04:48 -0500 Subject: [PATCH 12/12] QuietException should be a RuntimeException --- .../com/velocitypowered/proxy/util/except/QuietException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java b/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java index f095f5779..d16a00724 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java @@ -4,7 +4,7 @@ package com.velocitypowered.proxy.util.except; * A special-purpose exception thrown when we want to indicate an error condition but do not want * to see a large stack trace in logs. */ -public class QuietException extends Exception { +public class QuietException extends RuntimeException { public QuietException(String message) { super(message);