3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-16 21:10:30 +01:00

Extract player data forwarding to separate class (#1210)

* Extract player data forwarding to separate class

* Use modern switch statement
Dieser Commit ist enthalten in:
Riley Park 2024-01-28 19:05:21 -08:00 committet von GitHub
Ursprung b9b11665b9
Commit 4080ee2eaa
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
4 geänderte Dateien mit 221 neuen und 128 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,197 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection;
import static com.velocitypowered.proxy.VelocityServer.GENERAL_GSON;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.function.UnaryOperator;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.jspecify.annotations.Nullable;
@SuppressWarnings({"MissingJavadocMethod", "MissingJavadocType"})
public final class PlayerDataForwarding {
private static final String ALGORITHM = "HmacSHA256";
public static final String CHANNEL = "velocity:player_info";
public static final int MODERN_DEFAULT = 1;
public static final int MODERN_WITH_KEY = 2;
public static final int MODERN_WITH_KEY_V2 = 3;
public static final int MODERN_LAZY_SESSION = 4;
public static final int MODERN_MAX_VERSION = MODERN_LAZY_SESSION;
private static final char LEGACY_SEPARATOR = '\0';
private static final String BUNGEE_GUARD_TOKEN_PROPERTY_NAME = "bungeeguard-token";
private PlayerDataForwarding() {
}
public static ByteBuf createForwardingData(
final byte[] secret,
final String address,
final ProtocolVersion protocol,
final GameProfile profile,
final @Nullable IdentifiedKey key,
final int requestedVersion
) {
final ByteBuf forwarded = Unpooled.buffer(2048);
try {
final int actualVersion = findForwardingVersion(requestedVersion, protocol, key);
ProtocolUtils.writeVarInt(forwarded, actualVersion);
ProtocolUtils.writeString(forwarded, address);
ProtocolUtils.writeUuid(forwarded, profile.getId());
ProtocolUtils.writeString(forwarded, profile.getName());
ProtocolUtils.writeProperties(forwarded, profile.getProperties());
// This serves as additional redundancy. The key normally is stored in the
// login start to the server, but some setups require this.
if (actualVersion >= MODERN_WITH_KEY
&& actualVersion < MODERN_LAZY_SESSION) {
assert key != null;
ProtocolUtils.writePlayerKey(forwarded, key);
// Provide the signer UUID since the UUID may differ from the
// assigned UUID. Doing that breaks the signatures anyway but the server
// should be able to verify the key independently.
if (actualVersion >= MODERN_WITH_KEY_V2) {
if (key.getSignatureHolder() != null) {
forwarded.writeBoolean(true);
ProtocolUtils.writeUuid(forwarded, key.getSignatureHolder());
} else {
// Should only not be provided if the player was connected
// as offline-mode and the signer UUID was not backfilled
forwarded.writeBoolean(false);
}
}
}
final Mac mac = Mac.getInstance(ALGORITHM);
mac.init(new SecretKeySpec(secret, ALGORITHM));
mac.update(forwarded.array(), forwarded.arrayOffset(), forwarded.readableBytes());
final byte[] sig = mac.doFinal();
return Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(sig), forwarded);
} catch (final InvalidKeyException e) {
forwarded.release();
throw new RuntimeException("Unable to authenticate data", e);
} catch (final NoSuchAlgorithmException e) {
// Should never happen
forwarded.release();
throw new AssertionError(e);
}
}
private static int findForwardingVersion(
int requested,
final ProtocolVersion protocol,
final @Nullable IdentifiedKey key
) {
// Ensure we are in range
requested = Math.min(requested, MODERN_MAX_VERSION);
if (requested > MODERN_DEFAULT) {
if (protocol.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
return requested >= MODERN_LAZY_SESSION
? MODERN_LAZY_SESSION
: MODERN_DEFAULT;
}
if (key != null) {
return switch (key.getKeyRevision()) {
case GENERIC_V1 -> MODERN_WITH_KEY;
// Since V2 is not backwards compatible we have to throw the key if v2 and requested is v1
case LINKED_V2 -> requested >= MODERN_WITH_KEY_V2
? MODERN_WITH_KEY_V2
: MODERN_DEFAULT;
};
} else {
return MODERN_DEFAULT;
}
}
return MODERN_DEFAULT;
}
public static String createLegacyForwardingAddress(
final String serverAddress,
final String playerAddress,
final GameProfile profile
) {
return createLegacyForwardingAddress(
serverAddress,
playerAddress,
profile,
UnaryOperator.identity()
);
}
private static String createLegacyForwardingAddress(
final String serverAddress,
final String playerAddress,
final GameProfile profile,
final UnaryOperator<List<GameProfile.Property>> propertiesTransform
) {
// 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 (from Mojang).
final StringBuilder data = new StringBuilder()
.append(serverAddress)
.append(LEGACY_SEPARATOR)
.append(playerAddress)
.append(LEGACY_SEPARATOR)
.append(profile.getUndashedId())
.append(LEGACY_SEPARATOR);
GENERAL_GSON
.toJson(propertiesTransform.apply(profile.getProperties()), data);
return data.toString();
}
public static String createBungeeGuardForwardingAddress(
final String serverAddress,
final String playerAddress,
final GameProfile profile,
final byte[] forwardingSecret
) {
// Append forwarding secret as a BungeeGuard token.
final GameProfile.Property property = new GameProfile.Property(
BUNGEE_GUARD_TOKEN_PROPERTY_NAME,
new String(forwardingSecret, StandardCharsets.UTF_8),
""
);
return createLegacyForwardingAddress(
serverAddress,
playerAddress,
profile,
properties -> ImmutableList.<GameProfile.Property>builder()
.addAll(properties)
.add(property)
.build()
);
}
}

Datei anzeigen

@ -26,12 +26,5 @@ public class VelocityConstants {
throw new AssertionError();
}
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
public static final int MODERN_FORWARDING_DEFAULT = 1;
public static final int MODERN_FORWARDING_WITH_KEY = 2;
public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3;
public static final int MODERN_LAZY_SESSION = 4;
public static final int MODERN_FORWARDING_MAX_VERSION = MODERN_LAZY_SESSION;
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
}

Datei anzeigen

@ -19,19 +19,17 @@ package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.PlayerDataForwarding;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
@ -44,12 +42,7 @@ import com.velocitypowered.proxy.util.except.QuietRuntimeException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.CompletableFuture;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -86,15 +79,20 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
MinecraftConnection mc = serverConn.ensureConnected();
VelocityConfiguration configuration = server.getConfiguration();
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
&& packet.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
&& packet.getChannel().equals(PlayerDataForwarding.CHANNEL)) {
int requestedForwardingVersion = VelocityConstants.MODERN_FORWARDING_DEFAULT;
int requestedForwardingVersion = PlayerDataForwarding.MODERN_DEFAULT;
// Check version
if (packet.content().readableBytes() == 1) {
requestedForwardingVersion = packet.content().readByte();
}
ByteBuf forwardingData = createForwardingData(configuration.getForwardingSecret(),
serverConn.getPlayerRemoteAddressAsString(), serverConn.getPlayer(),
ConnectedPlayer player = serverConn.getPlayer();
ByteBuf forwardingData = PlayerDataForwarding.createForwardingData(
configuration.getForwardingSecret(),
serverConn.getPlayerRemoteAddressAsString(),
player.getProtocolVersion(),
player.getGameProfile(),
player.getIdentifiedKey(),
requestedForwardingVersion);
LoginPluginResponsePacket response = new LoginPluginResponsePacket(
@ -197,85 +195,4 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
);
}
}
private static int findForwardingVersion(int requested, ConnectedPlayer player) {
// Ensure we are in range
requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION);
if (requested > VelocityConstants.MODERN_FORWARDING_DEFAULT) {
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
return requested >= VelocityConstants.MODERN_LAZY_SESSION
? VelocityConstants.MODERN_LAZY_SESSION
: VelocityConstants.MODERN_FORWARDING_DEFAULT;
}
if (player.getIdentifiedKey() != null) {
// No enhanced switch on java 11
switch (player.getIdentifiedKey().getKeyRevision()) {
case GENERIC_V1:
return VelocityConstants.MODERN_FORWARDING_WITH_KEY;
// Since V2 is not backwards compatible we have to throw the key if v2 and requested is v1
case LINKED_V2:
return requested >= VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2
? VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2
: VelocityConstants.MODERN_FORWARDING_DEFAULT;
default:
return VelocityConstants.MODERN_FORWARDING_DEFAULT;
}
} else {
return VelocityConstants.MODERN_FORWARDING_DEFAULT;
}
}
return VelocityConstants.MODERN_FORWARDING_DEFAULT;
}
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
ConnectedPlayer player, int requestedVersion) {
ByteBuf forwarded = Unpooled.buffer(2048);
try {
int actualVersion = findForwardingVersion(requestedVersion, player);
ProtocolUtils.writeVarInt(forwarded, actualVersion);
ProtocolUtils.writeString(forwarded, address);
ProtocolUtils.writeUuid(forwarded, player.getGameProfile().getId());
ProtocolUtils.writeString(forwarded, player.getGameProfile().getName());
ProtocolUtils.writeProperties(forwarded, player.getGameProfile().getProperties());
// This serves as additional redundancy. The key normally is stored in the
// login start to the server, but some setups require this.
if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY
&& actualVersion < VelocityConstants.MODERN_LAZY_SESSION) {
IdentifiedKey key = player.getIdentifiedKey();
assert key != null;
ProtocolUtils.writePlayerKey(forwarded, key);
// Provide the signer UUID since the UUID may differ from the
// assigned UUID. Doing that breaks the signatures anyway but the server
// should be able to verify the key independently.
if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2) {
if (key.getSignatureHolder() != null) {
forwarded.writeBoolean(true);
ProtocolUtils.writeUuid(forwarded, key.getSignatureHolder());
} else {
// Should only not be provided if the player was connected
// as offline-mode and the signer UUID was not backfilled
forwarded.writeBoolean(false);
}
}
}
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
mac.update(forwarded.array(), forwarded.arrayOffset(), forwarded.readableBytes());
byte[] sig = mac.doFinal();
return Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(sig), forwarded);
} catch (InvalidKeyException e) {
forwarded.release();
throw new RuntimeException("Unable to authenticate data", e);
} catch (NoSuchAlgorithmException e) {
// Should never happen
forwarded.release();
throw new AssertionError(e);
}
}
}

Datei anzeigen

@ -17,24 +17,22 @@
package com.velocitypowered.proxy.connection.backend;
import static com.velocitypowered.proxy.VelocityServer.GENERAL_GSON;
import static com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN;
import static com.velocitypowered.proxy.network.Connections.HANDLER;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.util.GameProfile.Property;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.PlayerDataForwarding;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.forge.modern.ModernForgeConnectionType;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
@ -47,13 +45,10 @@ import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.UnaryOperator;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -142,32 +137,23 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
}
}
private String createLegacyForwardingAddress(UnaryOperator<List<Property>> propertiesTransform) {
// 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 (from Mojang).
StringBuilder data = new StringBuilder().append(proxyPlayer.getVirtualHost().orElseGet(() ->
registeredServer.getServerInfo().getAddress()).getHostString())
.append('\0')
.append(getPlayerRemoteAddressAsString())
.append('\0')
.append(proxyPlayer.getGameProfile().getUndashedId())
.append('\0');
GENERAL_GSON
.toJson(propertiesTransform.apply(proxyPlayer.getGameProfile().getProperties()), data);
return data.toString();
}
private String createLegacyForwardingAddress() {
return createLegacyForwardingAddress(UnaryOperator.identity());
return PlayerDataForwarding.createLegacyForwardingAddress(
proxyPlayer.getVirtualHost().orElseGet(() ->
registeredServer.getServerInfo().getAddress()).getHostString(),
getPlayerRemoteAddressAsString(),
proxyPlayer.getGameProfile()
);
}
private String createBungeeGuardForwardingAddress(byte[] forwardingSecret) {
// Append forwarding secret as a BungeeGuard token.
Property property =
new Property("bungeeguard-token", new String(forwardingSecret, StandardCharsets.UTF_8), "");
return createLegacyForwardingAddress(
properties -> ImmutableList.<Property>builder().addAll(properties).add(property).build());
return PlayerDataForwarding.createBungeeGuardForwardingAddress(
proxyPlayer.getVirtualHost().orElseGet(() ->
registeredServer.getServerInfo().getAddress()).getHostString(),
getPlayerRemoteAddressAsString(),
proxyPlayer.getGameProfile(),
forwardingSecret
);
}
private void startHandshake() {