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:
Ursprung
b9b11665b9
Commit
4080ee2eaa
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren