3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Improve server list ping, especially for legacy MC versions.

Dieser Commit ist enthalten in:
Andrew Steinborn 2018-08-07 01:02:39 -04:00
Ursprung df34c78b23
Commit fdf5f27da6
13 geänderte Dateien mit 169 neuen und 58 gelöschten Zeilen

Datei anzeigen

@ -32,6 +32,12 @@ public interface ProxyServer {
*/ */
Collection<Player> getAllPlayers(); Collection<Player> getAllPlayers();
/**
* Returns the number of players currently connected to this proxy.
* @return the players on this proxy
*/
int getPlayerCount();
/** /**
* Retrieves a registered {@link ServerInfo} instance by its name. * Retrieves a registered {@link ServerInfo} instance by its name.
* @param name the name of the server * @param name the name of the server

Datei anzeigen

@ -149,6 +149,11 @@ public class VelocityServer implements ProxyServer {
return ImmutableList.copyOf(connectionsByUuid.values()); return ImmutableList.copyOf(connectionsByUuid.values());
} }
@Override
public int getPlayerCount() {
return connectionsByUuid.size();
}
@Override @Override
public Optional<ServerInfo> getServerInfo(@Nonnull String name) { public Optional<ServerInfo> getServerInfo(@Nonnull String name) {
Preconditions.checkNotNull(name, "name"); Preconditions.checkNotNull(name, "name");

Datei anzeigen

@ -6,6 +6,7 @@ import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.protocol.PacketWrapper; import com.velocitypowered.proxy.protocol.PacketWrapper;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.JavaVelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipher; import com.velocitypowered.natives.encryption.VelocityCipher;
@ -167,8 +168,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
public void setProtocolVersion(int protocolVersion) { public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion); if (protocolVersion != ProtocolConstants.LEGACY) {
this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion); this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion);
this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion);
} else {
// Legacy handshake handling
this.channel.pipeline().remove(MINECRAFT_ENCODER);
this.channel.pipeline().remove(MINECRAFT_DECODER);
}
} }
public MinecraftSessionHandler getSessionHandler() { public MinecraftSessionHandler getSessionHandler() {

Datei anzeigen

@ -4,7 +4,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
public interface MinecraftSessionHandler { public interface MinecraftSessionHandler {
void handle(MinecraftPacket packet) throws Exception; void handle(MinecraftPacket packet);
default void handleUnknown(ByteBuf buf) { default void handleUnknown(ByteBuf buf) {
// No-op: we'll release the buffer later. // No-op: we'll release the buffer later.

Datei anzeigen

@ -1,14 +1,18 @@
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.VelocityServer;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.data.ServerPing;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.protocol.packet.Handshake; import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent; import net.kyori.text.TranslatableComponent;
import net.kyori.text.format.TextColor;
public class HandshakeSessionHandler implements MinecraftSessionHandler { public class HandshakeSessionHandler implements MinecraftSessionHandler {
private final MinecraftConnection connection; private final MinecraftConnection connection;
@ -19,6 +23,12 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
@Override @Override
public void handle(MinecraftPacket packet) { public void handle(MinecraftPacket packet) {
if (packet instanceof LegacyPing || packet instanceof LegacyHandshake) {
connection.setProtocolVersion(ProtocolConstants.LEGACY);
handleLegacy(packet);
return;
}
if (!(packet instanceof Handshake)) { if (!(packet instanceof Handshake)) {
throw new IllegalArgumentException("Did not expect packet " + packet.getClass().getName()); throw new IllegalArgumentException("Did not expect packet " + packet.getClass().getName());
} }
@ -43,6 +53,21 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
default: default:
throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus()); throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus());
} }
}
private void handleLegacy(MinecraftPacket packet) {
if (packet instanceof LegacyPing) {
VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration();
ServerPing ping = new ServerPing(
new ServerPing.Version(ProtocolConstants.MAXIMUM_GENERIC_VERSION, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()),
configuration.getMotdComponent(),
null
);
// The disconnect packet is the same as the server response one.
connection.closeWith(LegacyDisconnect.fromPingResponse(LegacyPingResponse.from(ping)));
} else if (packet instanceof LegacyHandshake) {
connection.closeWith(LegacyDisconnect.from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED)));
}
} }
} }

Datei anzeigen

@ -19,6 +19,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyPair; import java.security.KeyPair;
@ -53,7 +54,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
} }
@Override @Override
public void handle(MinecraftPacket packet) throws Exception { public void handle(MinecraftPacket packet) {
if (packet instanceof LoginPluginResponse) { if (packet instanceof LoginPluginResponse) {
LoginPluginResponse lpr = (LoginPluginResponse) packet; LoginPluginResponse lpr = (LoginPluginResponse) packet;
if (lpr.getId() == playerInfoId && lpr.isSuccess()) { if (lpr.getId() == playerInfoId && lpr.isSuccess()) {
@ -75,34 +76,41 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername())); handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername()));
} }
} else if (packet instanceof EncryptionResponse) { } else if (packet instanceof EncryptionResponse) {
KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair(); try {
EncryptionResponse response = (EncryptionResponse) packet; KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair();
byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken()); EncryptionResponse response = (EncryptionResponse) packet;
if (!Arrays.equals(verify, decryptedVerifyToken)) { byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken());
throw new IllegalStateException("Unable to successfully decrypt the verification token."); if (!Arrays.equals(verify, decryptedVerifyToken)) {
throw new IllegalStateException("Unable to successfully decrypt the verification token.");
}
byte[] decryptedSharedSecret = EncryptionUtils.decryptRsa(serverKeyPair, response.getSharedSecret());
String serverId = EncryptionUtils.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
String playerIp = ((InetSocketAddress) inbound.getChannel().remoteAddress()).getHostString();
VelocityServer.getServer().getHttpClient()
.get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp)))
.thenAcceptAsync(profileResponse -> {
try {
inbound.enableEncryption(decryptedSharedSecret);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
GameProfile profile = VelocityServer.GSON.fromJson(profileResponse, GameProfile.class);
handleSuccessfulLogin(profile);
}, inbound.getChannel().eventLoop())
.exceptionally(exception -> {
logger.error("Unable to enable encryption", exception);
inbound.close();
return null;
});
} catch (GeneralSecurityException e) {
logger.error("Unable to enable encryption", e);
inbound.close();
} catch (MalformedURLException e) {
throw new AssertionError(e);
} }
byte[] decryptedSharedSecret = EncryptionUtils.decryptRsa(serverKeyPair, response.getSharedSecret());
String serverId = EncryptionUtils.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
String playerIp = ((InetSocketAddress) inbound.getChannel().remoteAddress()).getHostString();
VelocityServer.getServer().getHttpClient()
.get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp)))
.thenAcceptAsync(profileResponse -> {
try {
inbound.enableEncryption(decryptedSharedSecret);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
GameProfile profile = VelocityServer.GSON.fromJson(profileResponse, GameProfile.class);
handleSuccessfulLogin(profile);
}, inbound.getChannel().eventLoop())
.exceptionally(exception -> {
logger.error("Unable to enable encryption", exception);
inbound.close();
return null;
});
} }
} }

Datei anzeigen

@ -35,9 +35,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration(); VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration();
// Status request // Status request
int shownVersion = ProtocolConstants.isSupported(connection.getProtocolVersion()) ? connection.getProtocolVersion() :
ProtocolConstants.MAXIMUM_GENERIC_VERSION;
ServerPing ping = new ServerPing( ServerPing ping = new ServerPing(
new ServerPing.Version(connection.getProtocolVersion(), "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
new ServerPing.Players(0, configuration.getShowMaxPlayers()), new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()),
configuration.getMotdComponent(), configuration.getMotdComponent(),
null null
); );

Datei anzeigen

@ -3,6 +3,8 @@ package com.velocitypowered.proxy.protocol;
import java.util.Arrays; import java.util.Arrays;
public enum ProtocolConstants { ; public enum ProtocolConstants { ;
public static final int LEGACY = -1;
public static final int MINECRAFT_1_8 = 47; public static final int MINECRAFT_1_8 = 47;
public static final int MINECRAFT_1_9 = 107; public static final int MINECRAFT_1_9 = 107;
public static final int MINECRAFT_1_9_1 = 108; public static final int MINECRAFT_1_9_1 = 108;

Datei anzeigen

@ -1,7 +1,10 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.PacketWrapper;
import com.velocitypowered.proxy.protocol.packet.LegacyHandshake;
import com.velocitypowered.proxy.protocol.packet.LegacyPing; import com.velocitypowered.proxy.protocol.packet.LegacyPing;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
@ -18,9 +21,12 @@ public class LegacyPingDecoder extends ByteToMessageDecoder {
short second = in.getUnsignedByte(in.readerIndex() + 1); short second = in.getUnsignedByte(in.readerIndex() + 1);
if (first == 0xfe && second == 0x01) { if (first == 0xfe && second == 0x01) {
in.skipBytes(in.readableBytes()); in.skipBytes(in.readableBytes());
out.add(new LegacyPing()); out.add(new PacketWrapper(new LegacyPing(), Unpooled.EMPTY_BUFFER));
} else if (first == 0x02) {
in.skipBytes(in.readableBytes());
out.add(new PacketWrapper(new LegacyHandshake(), Unpooled.EMPTY_BUFFER));
} else {
ctx.pipeline().remove(this);
} }
ctx.pipeline().remove(this);
} }
} }

Datei anzeigen

@ -1,39 +1,27 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Joiner; import com.velocitypowered.proxy.protocol.packet.LegacyDisconnect;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.proxy.protocol.packet.LegacyPingResponse;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List;
@ChannelHandler.Sharable @ChannelHandler.Sharable
public class LegacyPingEncoder extends MessageToByteEncoder<LegacyPingResponse> { public class LegacyPingEncoder extends MessageToByteEncoder<LegacyDisconnect> {
public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder(); public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder();
private LegacyPingEncoder() {} private LegacyPingEncoder() {}
@Override @Override
protected void encode(ChannelHandlerContext ctx, LegacyPingResponse msg, ByteBuf out) throws Exception { protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out) throws Exception {
out.writeByte(0xff); out.writeByte(0xff);
String serializedResponse = serialize(msg); writeLegacyString(out, msg.getReason());
out.writeShort(serializedResponse.length());
out.writeBytes(serializedResponse.getBytes(StandardCharsets.UTF_16BE));
} }
private String serialize(LegacyPingResponse response) { private static void writeLegacyString(ByteBuf out, String string) {
List<String> parts = ImmutableList.of( out.writeShort(string.length());
"§1", out.writeBytes(string.getBytes(StandardCharsets.UTF_16BE));
Integer.toString(response.getProtocolVersion()),
response.getServerVersion(),
response.getMotd(),
Integer.toString(response.getPlayersOnline()),
Integer.toString(response.getPlayersMax())
);
return Joiner.on('\0').join(parts);
} }
} }

Datei anzeigen

@ -0,0 +1,32 @@
package com.velocitypowered.proxy.protocol.packet;
import net.kyori.text.TextComponent;
import net.kyori.text.serializer.ComponentSerializers;
public class LegacyDisconnect {
private final String reason;
public LegacyDisconnect(String reason) {
this.reason = reason;
}
public static LegacyDisconnect fromPingResponse(LegacyPingResponse response) {
String kickMessage = String.join("\0",
"§1",
Integer.toString(response.getProtocolVersion()),
response.getServerVersion(),
response.getMotd(),
Integer.toString(response.getPlayersOnline()),
Integer.toString(response.getPlayersMax())
);
return new LegacyDisconnect(kickMessage);
}
public static LegacyDisconnect from(TextComponent component) {
return new LegacyDisconnect(ComponentSerializers.LEGACY.serialize(component));
}
public String getReason() {
return reason;
}
}

Datei anzeigen

@ -0,0 +1,17 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
public class LegacyHandshake implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
throw new UnsupportedOperationException();
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
throw new UnsupportedOperationException();
}
}

Datei anzeigen

@ -1,4 +1,17 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
public class LegacyPing { import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
public class LegacyPing implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
throw new UnsupportedOperationException();
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
throw new UnsupportedOperationException();
}
} }