3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-12-26 00:00:55 +01:00

Implement Velocity native IP forwarding for 1.13+

No other server implements this (as of yet). We plan to get support for
this into at least Paper and Sponge.
Dieser Commit ist enthalten in:
Andrew Steinborn 2018-07-30 23:40:25 -04:00
Ursprung a45adbc8ec
Commit d9f68140d6
7 geänderte Dateien mit 228 neuen und 21 gelöschten Zeilen

Datei anzeigen

@ -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";
}

Datei anzeigen

@ -3,14 +3,12 @@ package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packets.Disconnect; import com.velocitypowered.proxy.protocol.packets.*;
import com.velocitypowered.proxy.protocol.packets.EncryptionRequest;
import com.velocitypowered.proxy.protocol.packets.ServerLoginSuccess;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.packets.SetCompression;
public class LoginSessionHandler implements MinecraftSessionHandler { public class LoginSessionHandler implements MinecraftSessionHandler {
private final ServerConnection connection; private final ServerConnection connection;
private int forwardingPacketId = -1;
public LoginSessionHandler(ServerConnection connection) { public LoginSessionHandler(ServerConnection connection) {
this.connection = connection; this.connection = connection;
@ -20,20 +18,21 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
public void handle(MinecraftPacket packet) { public void handle(MinecraftPacket packet) {
if (packet instanceof EncryptionRequest) { if (packet instanceof EncryptionRequest) {
throw new IllegalStateException("Backend server is online-mode!"); throw new IllegalStateException("Backend server is online-mode!");
} 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?");
} }
}
if (packet instanceof Disconnect) { } else if (packet instanceof Disconnect) {
Disconnect disconnect = (Disconnect) packet; Disconnect disconnect = (Disconnect) packet;
connection.disconnect(); connection.disconnect();
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), disconnect); connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), disconnect);
} } else if (packet instanceof SetCompression) {
if (packet instanceof SetCompression) {
SetCompression sc = (SetCompression) packet; SetCompression sc = (SetCompression) packet;
connection.getChannel().setCompressionThreshold(sc.getThreshold()); connection.getChannel().setCompressionThreshold(sc.getThreshold());
} } else if (packet instanceof ServerLoginSuccess) {
if (packet instanceof ServerLoginSuccess) {
// The player has been logged on to the backend server. // The player has been logged on to the backend server.
connection.getChannel().setState(StateRegistry.PLAY); connection.getChannel().setState(StateRegistry.PLAY);
ServerConnection existingConnection = connection.getProxyPlayer().getConnectedServer(); ServerConnection existingConnection = connection.getProxyPlayer().getConnectedServer();
@ -53,4 +52,12 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
public void exception(Throwable throwable) { public void exception(Throwable throwable) {
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), throwable); connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), throwable);
} }
public int getForwardingPacketId() {
return forwardingPacketId;
}
public void setForwardingPacketId(int forwardingPacketId) {
this.forwardingPacketId = forwardingPacketId;
}
} }

Datei anzeigen

@ -2,23 +2,28 @@ package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.proxy.config.IPForwardingMode; import com.velocitypowered.proxy.config.IPForwardingMode;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; 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.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.packets.Handshake; 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.protocol.packets.ServerLogin;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.data.ServerInfo; import com.velocitypowered.proxy.data.ServerInfo;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*; import io.netty.channel.*;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.List; import java.util.concurrent.ThreadLocalRandom;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.velocitypowered.network.Connections.FRAME_DECODER; import static com.velocitypowered.network.Connections.FRAME_DECODER;
@ -99,9 +104,29 @@ public class ServerConnection implements MinecraftConnectionAssociation {
handshake.setPort(serverInfo.getAddress().getPort()); handshake.setPort(serverInfo.getAddress().getPort());
channel.write(handshake); channel.write(handshake);
channel.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); int protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
channel.setProtocolVersion(protocolVersion);
channel.setState(StateRegistry.LOGIN); 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 // Login
ServerLogin login = new ServerLogin(); ServerLogin login = new ServerLogin();
login.setUsername(proxyPlayer.getUsername()); login.setUsername(proxyPlayer.getUsername());
@ -129,4 +154,24 @@ public class ServerConnection implements MinecraftConnectionAssociation {
public String toString() { public String toString() {
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName(); 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;
}
} }

Datei anzeigen

@ -1,6 +1,7 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.data.GameProfile; import com.velocitypowered.proxy.data.GameProfile;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; 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.data.ServerInfo;
import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.EncryptionUtils;
import com.velocitypowered.proxy.util.UuidUtils; import com.velocitypowered.proxy.util.UuidUtils;
import io.netty.buffer.Unpooled;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -27,7 +29,6 @@ import java.util.concurrent.ThreadLocalRandom;
public class LoginSessionHandler implements MinecraftSessionHandler { public class LoginSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
private static final String MOJANG_SERVER_AUTH_URL = private static final String MOJANG_SERVER_AUTH_URL =
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
@ -41,7 +42,22 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override @Override
public void handle(MinecraftPacket packet) throws Exception { 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; this.login = (ServerLogin) packet;
if (VelocityServer.getServer().getConfiguration().isOnlineMode()) { if (VelocityServer.getServer().getConfiguration().isOnlineMode()) {
@ -53,9 +69,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// Offline-mode, don't try to request encryption. // Offline-mode, don't try to request encryption.
handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername())); handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername()));
} }
} } else if (packet instanceof EncryptionResponse) {
if (packet instanceof EncryptionResponse) {
KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair(); KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair();
EncryptionResponse response = (EncryptionResponse) packet; EncryptionResponse response = (EncryptionResponse) packet;
byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken()); byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken());

Datei anzeigen

@ -108,6 +108,8 @@ public enum StateRegistry {
genericMappings(0x00)); genericMappings(0x00));
SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new, SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new,
genericMappings(0x01)); genericMappings(0x01));
SERVERBOUND.register(LoginPluginMessage.class, LoginPluginMessage::new,
map(0x02, MINECRAFT_1_13));
CLIENTBOUND.register(Disconnect.class, Disconnect::new, CLIENTBOUND.register(Disconnect.class, Disconnect::new,
genericMappings(0x00)); genericMappings(0x00));
@ -117,6 +119,8 @@ public enum StateRegistry {
genericMappings(0x02)); genericMappings(0x02));
CLIENTBOUND.register(SetCompression.class, SetCompression::new, CLIENTBOUND.register(SetCompression.class, SetCompression::new,
genericMappings(0x03)); genericMappings(0x03));
CLIENTBOUND.register(LoginPluginResponse.class, LoginPluginResponse::new,
map(0x04, MINECRAFT_1_13));
} }
}; };

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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);
}
}