diff --git a/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java b/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java new file mode 100644 index 000000000..f95053e7d --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java @@ -0,0 +1,9 @@ +package com.velocitypowered.proxy.connection; + +public class VelocityConstants { + private VelocityConstants() { + throw new AssertionError(); + } + + public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info"; +} diff --git a/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index cf1b72639..feee8649a 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -3,14 +3,12 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.proxy.protocol.packets.Disconnect; -import com.velocitypowered.proxy.protocol.packets.EncryptionRequest; -import com.velocitypowered.proxy.protocol.packets.ServerLoginSuccess; +import com.velocitypowered.proxy.protocol.packets.*; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.protocol.packets.SetCompression; public class LoginSessionHandler implements MinecraftSessionHandler { private final ServerConnection connection; + private int forwardingPacketId = -1; public LoginSessionHandler(ServerConnection connection) { this.connection = connection; @@ -20,20 +18,21 @@ public class LoginSessionHandler implements MinecraftSessionHandler { public void handle(MinecraftPacket packet) { if (packet instanceof EncryptionRequest) { throw new IllegalStateException("Backend server is online-mode!"); - } - - if (packet instanceof Disconnect) { + } else if (packet instanceof LoginPluginResponse) { + LoginPluginResponse lpr = (LoginPluginResponse) packet; + if (lpr.getId() == forwardingPacketId) { + if (!lpr.isSuccess()) { + throw new IllegalStateException("Unable to forward player information to server! Is it configured properly?"); + } + } + } else if (packet instanceof Disconnect) { Disconnect disconnect = (Disconnect) packet; connection.disconnect(); connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), disconnect); - } - - if (packet instanceof SetCompression) { + } else if (packet instanceof SetCompression) { SetCompression sc = (SetCompression) packet; connection.getChannel().setCompressionThreshold(sc.getThreshold()); - } - - if (packet instanceof ServerLoginSuccess) { + } else if (packet instanceof ServerLoginSuccess) { // The player has been logged on to the backend server. connection.getChannel().setState(StateRegistry.PLAY); ServerConnection existingConnection = connection.getProxyPlayer().getConnectedServer(); @@ -53,4 +52,12 @@ public class LoginSessionHandler implements MinecraftSessionHandler { public void exception(Throwable throwable) { connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), throwable); } + + public int getForwardingPacketId() { + return forwardingPacketId; + } + + public void setForwardingPacketId(int forwardingPacketId) { + this.forwardingPacketId = forwardingPacketId; + } } diff --git a/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java b/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java index 6fb2ed13e..0f87e9b75 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java @@ -2,23 +2,28 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.proxy.config.IPForwardingMode; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; +import com.velocitypowered.proxy.connection.VelocityConstants; +import com.velocitypowered.proxy.data.GameProfile; import com.velocitypowered.proxy.protocol.ProtocolConstants; +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.packets.Handshake; +import com.velocitypowered.proxy.protocol.packets.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packets.ServerLogin; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.data.ServerInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.handler.timeout.ReadTimeoutHandler; -import java.util.List; -import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import static com.velocitypowered.network.Connections.FRAME_DECODER; @@ -99,9 +104,29 @@ public class ServerConnection implements MinecraftConnectionAssociation { handshake.setPort(serverInfo.getAddress().getPort()); channel.write(handshake); - channel.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); + int protocolVersion = proxyPlayer.getConnection().getProtocolVersion(); + channel.setProtocolVersion(protocolVersion); channel.setState(StateRegistry.LOGIN); + // 1.13 stuff + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) { + if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN) { + // Velocity's IP forwarding includes the player's IP address and their game profile. + GameProfile profile = proxyPlayer.getProfile(); + ByteBuf buf = createForwardingData(proxyPlayer.getRemoteAddress().getHostString(), profile); + + // Send the message on + LoginPluginMessage forwarding = new LoginPluginMessage(); + forwarding.setId(ThreadLocalRandom.current().nextInt()); + forwarding.setChannel(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL); + forwarding.setData(buf); + + LoginSessionHandler lsh = (LoginSessionHandler) channel.getSessionHandler(); + lsh.setForwardingPacketId(forwarding.getId()); + channel.write(forwarding); + } + } + // Login ServerLogin login = new ServerLogin(); login.setUsername(proxyPlayer.getUsername()); @@ -129,4 +154,24 @@ public class ServerConnection implements MinecraftConnectionAssociation { public String toString() { return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName(); } + + private static ByteBuf createForwardingData(String address, GameProfile profile) { + ByteBuf buf = Unpooled.buffer(); + ProtocolUtils.writeString(buf, address); + ProtocolUtils.writeString(buf, profile.getName()); + ProtocolUtils.writeString(buf, profile.idAsUuid().toString()); + ProtocolUtils.writeVarInt(buf, profile.getProperties().size()); + for (GameProfile.Property property : profile.getProperties()) { + ProtocolUtils.writeString(buf, property.getName()); + ProtocolUtils.writeString(buf, property.getValue()); + String signature = property.getSignature(); + if (signature != null) { + buf.writeBoolean(true); + ProtocolUtils.writeString(buf, signature); + } else { + buf.writeBoolean(false); + } + } + return buf; + } } diff --git a/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index c3ebac385..c8b4637c4 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -1,6 +1,7 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.base.Preconditions; +import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.data.GameProfile; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; @@ -12,6 +13,7 @@ import com.velocitypowered.proxy.connection.backend.ServerConnection; import com.velocitypowered.proxy.data.ServerInfo; import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.UuidUtils; +import io.netty.buffer.Unpooled; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import org.apache.logging.log4j.LogManager; @@ -27,7 +29,6 @@ import java.util.concurrent.ThreadLocalRandom; public class LoginSessionHandler implements MinecraftSessionHandler { private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); - private static final String MOJANG_SERVER_AUTH_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; @@ -41,7 +42,22 @@ public class LoginSessionHandler implements MinecraftSessionHandler { @Override public void handle(MinecraftPacket packet) throws Exception { - if (packet instanceof ServerLogin) { + if (packet instanceof LoginPluginMessage) { + LoginPluginMessage lpm = (LoginPluginMessage) packet; + if (lpm.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) { + // Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening. + inbound.closeWith(Disconnect.create( + TextComponent.of("Running Velocity behind Velocity isn't supported.", TextColor.RED) + )); + } else { + // We don't know what this message is. + LoginPluginResponse response = new LoginPluginResponse(); + response.setId(lpm.getId()); + response.setSuccess(false); + response.setData(Unpooled.EMPTY_BUFFER); + inbound.write(response); + } + } else if (packet instanceof ServerLogin) { this.login = (ServerLogin) packet; if (VelocityServer.getServer().getConfiguration().isOnlineMode()) { @@ -53,9 +69,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { // Offline-mode, don't try to request encryption. handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername())); } - } - - if (packet instanceof EncryptionResponse) { + } else if (packet instanceof EncryptionResponse) { KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair(); EncryptionResponse response = (EncryptionResponse) packet; byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken()); diff --git a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 33fa678fa..8a5be3bd0 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -108,6 +108,8 @@ public enum StateRegistry { genericMappings(0x00)); SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new, genericMappings(0x01)); + SERVERBOUND.register(LoginPluginMessage.class, LoginPluginMessage::new, + map(0x02, MINECRAFT_1_13)); CLIENTBOUND.register(Disconnect.class, Disconnect::new, genericMappings(0x00)); @@ -117,6 +119,8 @@ public enum StateRegistry { genericMappings(0x02)); CLIENTBOUND.register(SetCompression.class, SetCompression::new, genericMappings(0x03)); + CLIENTBOUND.register(LoginPluginResponse.class, LoginPluginResponse::new, + map(0x04, MINECRAFT_1_13)); } }; diff --git a/src/main/java/com/velocitypowered/proxy/protocol/packets/LoginPluginMessage.java b/src/main/java/com/velocitypowered/proxy/protocol/packets/LoginPluginMessage.java new file mode 100644 index 000000000..c0fbdfa8e --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/protocol/packets/LoginPluginMessage.java @@ -0,0 +1,64 @@ +package com.velocitypowered.proxy.protocol.packets; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public class LoginPluginMessage implements MinecraftPacket { + private int id; + private String channel; + private ByteBuf data; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public ByteBuf getData() { + return data; + } + + public void setData(ByteBuf data) { + this.data = data; + } + + @Override + public String toString() { + return "LoginPluginMessage{" + + "id=" + id + + ", channel='" + channel + '\'' + + ", data=" + data + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.id = ProtocolUtils.readVarInt(buf); + this.channel = ProtocolUtils.readString(buf); + if (buf.isReadable()) { + this.data = buf.readRetainedSlice(buf.readableBytes()); + } else { + this.data = Unpooled.EMPTY_BUFFER; + } + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeVarInt(buf, id); + ProtocolUtils.writeString(buf, channel); + buf.writeBytes(data); + } +} diff --git a/src/main/java/com/velocitypowered/proxy/protocol/packets/LoginPluginResponse.java b/src/main/java/com/velocitypowered/proxy/protocol/packets/LoginPluginResponse.java new file mode 100644 index 000000000..d60247b75 --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/protocol/packets/LoginPluginResponse.java @@ -0,0 +1,64 @@ +package com.velocitypowered.proxy.protocol.packets; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public class LoginPluginResponse implements MinecraftPacket { + private int id; + private boolean success; + private ByteBuf data; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public ByteBuf getData() { + return data; + } + + public void setData(ByteBuf data) { + this.data = data; + } + + @Override + public String toString() { + return "LoginPluginResponse{" + + "id=" + id + + ", success=" + success + + ", data=" + data + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.id = ProtocolUtils.readVarInt(buf); + this.success = buf.readBoolean(); + if (buf.isReadable()) { + this.data = buf.readRetainedSlice(buf.readableBytes()); + } else { + this.data = Unpooled.EMPTY_BUFFER; + } + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeVarInt(buf, id); + buf.writeBoolean(success); + buf.writeBytes(data); + } +}