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 b5f69b1d6..b4ecdfa60 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java @@ -8,6 +8,7 @@ import com.velocitypowered.proxy.data.ServerInfo; import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.util.UuidUtils; import io.netty.channel.*; public class ServerConnection { @@ -51,12 +52,23 @@ public class ServerConnection { }); } + private String createBungeeForwardingAddress() { + // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, + // separated by \0 (the null byte). In order, you send the original host, the player's IP, their + // UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang). + // + // Velocity doesn't yet support online-mode, unfortunately. That will come soon. + return serverInfo.getAddress().getHostString() + "\0" + + proxyPlayer.getRemoteAddress().getHostString() + "\0" + + UuidUtils.toUndashed(proxyPlayer.getUniqueId()); + } + private void startHandshake() { // Initiate a handshake. Handshake handshake = new Handshake(); handshake.setNextStatus(2); // login handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); - handshake.setServerAddress(serverInfo.getAddress().getHostString()); + handshake.setServerAddress(createBungeeForwardingAddress()); handshake.setPort(serverInfo.getAddress().getPort()); channel.write(handshake); diff --git a/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index a7b6053f7..385206b2b 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -10,6 +10,7 @@ import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import net.kyori.text.serializer.ComponentSerializers; +import java.net.InetSocketAddress; import java.util.UUID; public class ConnectedPlayer { @@ -36,6 +37,10 @@ public class ConnectedPlayer { return connection; } + public InetSocketAddress getRemoteAddress() { + return (InetSocketAddress) connection.getChannel().remoteAddress(); + } + public ServerConnection getConnectedServer() { return connectedServer; } @@ -57,9 +62,7 @@ public class ConnectedPlayer { // The player isn't yet connected to a server - we should disconnect them. connection.closeWith(Disconnect.create(component)); } else { - Chat chat = new Chat(); - chat.setMessage(ComponentSerializers.JSON.serialize(component)); - connection.write(chat); + connection.write(Chat.create(component)); } } diff --git a/src/main/java/com/velocitypowered/proxy/protocol/packets/Chat.java b/src/main/java/com/velocitypowered/proxy/protocol/packets/Chat.java index f6b318643..5a72d9feb 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/packets/Chat.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/packets/Chat.java @@ -1,14 +1,25 @@ package com.velocitypowered.proxy.protocol.packets; +import com.google.common.base.Preconditions; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.ComponentSerializers; public class Chat implements MinecraftPacket { private String message; private byte position; + public Chat() { + } + + public Chat(String message, byte position) { + this.message = message; + this.position = position; + } + public String getMessage() { return message; } @@ -48,4 +59,13 @@ public class Chat implements MinecraftPacket { buf.writeByte(position); } } + + public static Chat create(TextComponent component) { + return create(component, (byte) 0); + } + + public static Chat create(TextComponent component, byte pos) { + Preconditions.checkNotNull(component, "component"); + return new Chat(ComponentSerializers.JSON.serialize(component), pos); + } } diff --git a/src/main/java/com/velocitypowered/proxy/util/UuidUtils.java b/src/main/java/com/velocitypowered/proxy/util/UuidUtils.java index 963a57f21..d42810ab2 100644 --- a/src/main/java/com/velocitypowered/proxy/util/UuidUtils.java +++ b/src/main/java/com/velocitypowered/proxy/util/UuidUtils.java @@ -18,6 +18,11 @@ public enum UuidUtils { ); } + public static String toUndashed(final UUID uuid) { + Preconditions.checkNotNull(uuid, "uuid"); + return Long.toUnsignedString(uuid.getMostSignificantBits(), 16) + Long.toUnsignedString(uuid.getLeastSignificantBits(), 16); + } + public static UUID generateOfflinePlayerUuid(String username) { return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); } diff --git a/src/test/java/com/velocitypowered/proxy/util/UuidUtilsTest.java b/src/test/java/com/velocitypowered/proxy/util/UuidUtilsTest.java index 2afed9251..94f0b90cb 100644 --- a/src/test/java/com/velocitypowered/proxy/util/UuidUtilsTest.java +++ b/src/test/java/com/velocitypowered/proxy/util/UuidUtilsTest.java @@ -14,12 +14,17 @@ class UuidUtilsTest { private static final String TEST_OFFLINE_PLAYER = "tuxed"; @Test - void testFromUndashed() { + void generateOfflinePlayerUuid() { + assertEquals(TEST_OFFLINE_PLAYER_UUID, UuidUtils.generateOfflinePlayerUuid(TEST_OFFLINE_PLAYER), "UUIDs do not match"); + } + + @Test + void fromUndashed() { assertEquals(EXPECTED_DASHED_UUID, UuidUtils.fromUndashed(ACTUAL_UNDASHED_UUID), "UUIDs do not match"); } @Test - void generateOfflinePlayerUuid() { - assertEquals(TEST_OFFLINE_PLAYER_UUID, UuidUtils.generateOfflinePlayerUuid(TEST_OFFLINE_PLAYER), "UUIDs do not match"); + void toUndashed() { + assertEquals(ACTUAL_UNDASHED_UUID, UuidUtils.toUndashed(EXPECTED_DASHED_UUID), "UUIDs do not match"); } }