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:
Ursprung
df34c78b23
Commit
fdf5f27da6
@ -32,6 +32,12 @@ public interface ProxyServer {
|
||||
*/
|
||||
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.
|
||||
* @param name the name of the server
|
||||
|
@ -149,6 +149,11 @@ public class VelocityServer implements ProxyServer {
|
||||
return ImmutableList.copyOf(connectionsByUuid.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPlayerCount() {
|
||||
return connectionsByUuid.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ServerInfo> getServerInfo(@Nonnull String name) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
|
@ -6,6 +6,7 @@ import com.velocitypowered.natives.encryption.VelocityCipherFactory;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.protocol.PacketWrapper;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
@ -167,8 +168,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion);
|
||||
this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion);
|
||||
if (protocolVersion != ProtocolConstants.LEGACY) {
|
||||
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() {
|
||||
|
@ -4,7 +4,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public interface MinecraftSessionHandler {
|
||||
void handle(MinecraftPacket packet) throws Exception;
|
||||
void handle(MinecraftPacket packet);
|
||||
|
||||
default void handleUnknown(ByteBuf buf) {
|
||||
// No-op: we'll release the buffer later.
|
||||
|
@ -1,14 +1,18 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
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.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.data.ServerPing;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
|
||||
public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
private final MinecraftConnection connection;
|
||||
@ -19,6 +23,12 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
if (packet instanceof LegacyPing || packet instanceof LegacyHandshake) {
|
||||
connection.setProtocolVersion(ProtocolConstants.LEGACY);
|
||||
handleLegacy(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(packet instanceof Handshake)) {
|
||||
throw new IllegalArgumentException("Did not expect packet " + packet.getClass().getName());
|
||||
}
|
||||
@ -43,6 +53,21 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
default:
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
@ -53,7 +54,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) throws Exception {
|
||||
public void handle(MinecraftPacket packet) {
|
||||
if (packet instanceof LoginPluginResponse) {
|
||||
LoginPluginResponse lpr = (LoginPluginResponse) packet;
|
||||
if (lpr.getId() == playerInfoId && lpr.isSuccess()) {
|
||||
@ -75,34 +76,41 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername()));
|
||||
}
|
||||
} else if (packet instanceof EncryptionResponse) {
|
||||
KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair();
|
||||
EncryptionResponse response = (EncryptionResponse) packet;
|
||||
byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken());
|
||||
if (!Arrays.equals(verify, decryptedVerifyToken)) {
|
||||
throw new IllegalStateException("Unable to successfully decrypt the verification token.");
|
||||
try {
|
||||
KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair();
|
||||
EncryptionResponse response = (EncryptionResponse) packet;
|
||||
byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken());
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,9 +35,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration();
|
||||
|
||||
// Status request
|
||||
int shownVersion = ProtocolConstants.isSupported(connection.getProtocolVersion()) ? connection.getProtocolVersion() :
|
||||
ProtocolConstants.MAXIMUM_GENERIC_VERSION;
|
||||
ServerPing ping = new ServerPing(
|
||||
new ServerPing.Version(connection.getProtocolVersion(), "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
|
||||
new ServerPing.Players(0, configuration.getShowMaxPlayers()),
|
||||
new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
|
||||
new ServerPing.Players(VelocityServer.getServer().getPlayerCount(), configuration.getShowMaxPlayers()),
|
||||
configuration.getMotdComponent(),
|
||||
null
|
||||
);
|
||||
|
@ -3,6 +3,8 @@ package com.velocitypowered.proxy.protocol;
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum ProtocolConstants { ;
|
||||
public static final int LEGACY = -1;
|
||||
|
||||
public static final int MINECRAFT_1_8 = 47;
|
||||
public static final int MINECRAFT_1_9 = 107;
|
||||
public static final int MINECRAFT_1_9_1 = 108;
|
||||
|
@ -1,7 +1,10 @@
|
||||
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 io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
@ -18,9 +21,12 @@ public class LegacyPingDecoder extends ByteToMessageDecoder {
|
||||
short second = in.getUnsignedByte(in.readerIndex() + 1);
|
||||
if (first == 0xfe && second == 0x01) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +1,27 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyPingResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyDisconnect;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public class LegacyPingEncoder extends MessageToByteEncoder<LegacyPingResponse> {
|
||||
public class LegacyPingEncoder extends MessageToByteEncoder<LegacyDisconnect> {
|
||||
public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder();
|
||||
|
||||
private LegacyPingEncoder() {}
|
||||
|
||||
@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);
|
||||
String serializedResponse = serialize(msg);
|
||||
out.writeShort(serializedResponse.length());
|
||||
out.writeBytes(serializedResponse.getBytes(StandardCharsets.UTF_16BE));
|
||||
writeLegacyString(out, msg.getReason());
|
||||
}
|
||||
|
||||
private String serialize(LegacyPingResponse response) {
|
||||
List<String> parts = ImmutableList.of(
|
||||
"§1",
|
||||
Integer.toString(response.getProtocolVersion()),
|
||||
response.getServerVersion(),
|
||||
response.getMotd(),
|
||||
Integer.toString(response.getPlayersOnline()),
|
||||
Integer.toString(response.getPlayersMax())
|
||||
);
|
||||
return Joiner.on('\0').join(parts);
|
||||
private static void writeLegacyString(ByteBuf out, String string) {
|
||||
out.writeShort(string.length());
|
||||
out.writeBytes(string.getBytes(StandardCharsets.UTF_16BE));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,4 +1,17 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren