Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-16 21:10:30 +01:00
Implement a proper login state machine for logins
Fixes #688 and probably a host of other potential problems.
Dieser Commit ist enthalten in:
Ursprung
5ee96606f6
Commit
aa38d3e561
@ -34,9 +34,8 @@ import com.velocitypowered.natives.encryption.VelocityCipherFactory;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.client.LoginSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.client.InitialLoginSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.client.StatusSessionHandler;
|
||||
import com.velocitypowered.proxy.network.Connections;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.VelocityConnectionEvent;
|
||||
@ -178,7 +177,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
if (cause instanceof ReadTimeoutException) {
|
||||
logger.error("{}: read timed out", association);
|
||||
} else {
|
||||
boolean frontlineHandler = sessionHandler instanceof LoginSessionHandler
|
||||
boolean frontlineHandler = sessionHandler instanceof InitialLoginSessionHandler
|
||||
|| sessionHandler instanceof HandshakeSessionHandler
|
||||
|| sessionHandler instanceof StatusSessionHandler;
|
||||
boolean isQuietDecoderException = cause instanceof QuietDecoderException;
|
||||
|
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.client;
|
||||
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
|
||||
import com.velocitypowered.api.permission.PermissionFunction;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.util.UuidUtils;
|
||||
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.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(AuthSessionHandler.class);
|
||||
|
||||
private final VelocityServer server;
|
||||
private final MinecraftConnection mcConnection;
|
||||
private final LoginInboundConnection inbound;
|
||||
private GameProfile profile;
|
||||
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
|
||||
private final boolean onlineMode;
|
||||
|
||||
AuthSessionHandler(VelocityServer server, LoginInboundConnection inbound,
|
||||
GameProfile profile, boolean onlineMode) {
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||
this.profile = Preconditions.checkNotNull(profile, "profile");
|
||||
this.onlineMode = onlineMode;
|
||||
this.mcConnection = inbound.delegatedConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
// Some connection types may need to alter the game profile.
|
||||
profile = mcConnection.getType().addGameProfileTokensIfRequired(profile,
|
||||
server.getConfiguration().getPlayerInfoForwardingMode());
|
||||
GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(inbound, profile,
|
||||
onlineMode);
|
||||
final GameProfile finalProfile = profile;
|
||||
|
||||
server.getEventManager().fire(profileRequestEvent).thenComposeAsync(profileEvent -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
// The player disconnected after we authenticated them.
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
|
||||
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode);
|
||||
this.connectedPlayer = player;
|
||||
if (!server.canRegisterConnection(player)) {
|
||||
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy",
|
||||
NamedTextColor.RED), true);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
|
||||
return server.getEventManager()
|
||||
.fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (!mcConnection.isClosed()) {
|
||||
// wait for permissions to load, then set the players permission function
|
||||
final PermissionFunction function = event.createFunction(player);
|
||||
if (function == null) {
|
||||
logger.error(
|
||||
"A plugin permission provider {} provided an invalid permission function"
|
||||
+ " for player {}. This is a bug in the plugin, not in Velocity. Falling"
|
||||
+ " back to the default permission function.",
|
||||
event.getProvider().getClass().getName(),
|
||||
player.getUsername());
|
||||
} else {
|
||||
player.setPermissionFunction(function);
|
||||
}
|
||||
completeLoginProtocolPhaseAndInitialize(player);
|
||||
}
|
||||
}, mcConnection.eventLoop());
|
||||
}, mcConnection.eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception during connection of {}", finalProfile, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) {
|
||||
int threshold = server.getConfiguration().getCompressionThreshold();
|
||||
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
||||
mcConnection.write(new SetCompression(threshold));
|
||||
mcConnection.setCompressionThreshold(threshold);
|
||||
}
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
UUID playerUniqueId = player.getUniqueId();
|
||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) {
|
||||
playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername());
|
||||
}
|
||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||
success.setUsername(player.getUsername());
|
||||
success.setUuid(playerUniqueId);
|
||||
mcConnection.write(success);
|
||||
|
||||
mcConnection.setAssociation(player);
|
||||
mcConnection.setState(StateRegistry.PLAY);
|
||||
|
||||
server.getEventManager().fire(new LoginEvent(player))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
// The player was disconnected
|
||||
server.getEventManager().fireAndForget(new DisconnectEvent(player,
|
||||
DisconnectEvent.LoginStatus.CANCELLED_BY_USER_BEFORE_COMPLETE));
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Component> reason = event.getResult().getReasonComponent();
|
||||
if (reason.isPresent()) {
|
||||
player.disconnect0(reason.get(), true);
|
||||
} else {
|
||||
if (!server.registerConnection(player)) {
|
||||
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"),
|
||||
true);
|
||||
return;
|
||||
}
|
||||
|
||||
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
server.getEventManager().fire(new PostLoginEvent(player))
|
||||
.thenCompose((ignored) -> connectToInitialServer(player))
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while connecting {} to initial server", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}, mcConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while completing login initialisation phase for {}", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> connectToInitialServer(ConnectedPlayer player) {
|
||||
Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
|
||||
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player,
|
||||
initialFromConfig.orElse(null));
|
||||
|
||||
return server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
Optional<RegisteredServer> toTry = event.getInitialServer();
|
||||
if (!toTry.isPresent()) {
|
||||
player.disconnect0(Component.translatable("velocity.error.no-available-servers",
|
||||
NamedTextColor.RED), true);
|
||||
return;
|
||||
}
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
}, mcConnection.eventLoop());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
mcConnection.close(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
if (connectedPlayer != null) {
|
||||
connectedPlayer.teardown();
|
||||
}
|
||||
this.inbound.cleanup();
|
||||
}
|
||||
}
|
@ -40,8 +40,6 @@ import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -144,7 +142,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
LoginInboundConnection lic = new LoginInboundConnection(ic);
|
||||
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic));
|
||||
connection.setSessionHandler(new LoginSessionHandler(server, connection, lic));
|
||||
connection.setSessionHandler(new InitialLoginSessionHandler(server, connection, lic));
|
||||
}
|
||||
|
||||
private ConnectionType getHandshakeConnectionType(Handshake handshake) {
|
||||
|
@ -18,38 +18,23 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
||||
import static com.velocitypowered.proxy.VelocityServer.GENERAL_GSON;
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
|
||||
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent.LoginStatus;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
|
||||
import com.velocitypowered.api.permission.PermissionFunction;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.util.UuidUtils;
|
||||
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.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.GeneralSecurityException;
|
||||
@ -57,8 +42,6 @@ import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import net.kyori.adventure.text.Component;
|
||||
@ -69,9 +52,9 @@ import org.asynchttpclient.ListenableFuture;
|
||||
import org.asynchttpclient.Response;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
||||
private static final Logger logger = LogManager.getLogger(InitialLoginSessionHandler.class);
|
||||
private static final String MOJANG_HASJOINED_URL =
|
||||
System.getProperty("mojang.sessionserver", "https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
||||
.concat("?username=%s&serverId=%s");
|
||||
@ -81,9 +64,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
private final LoginInboundConnection inbound;
|
||||
private @MonotonicNonNull ServerLogin login;
|
||||
private byte[] verify = EMPTY_BYTE_ARRAY;
|
||||
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
|
||||
private LoginState currentState = LoginState.LOGIN_PACKET_EXPECTED;
|
||||
|
||||
LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
|
||||
InitialLoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
|
||||
LoginInboundConnection inbound) {
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection");
|
||||
@ -92,8 +75,53 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(ServerLogin packet) {
|
||||
assertState(LoginState.LOGIN_PACKET_EXPECTED);
|
||||
this.currentState = LoginState.LOGIN_PACKET_RECEIVED;
|
||||
this.login = packet;
|
||||
beginPreLogin();
|
||||
|
||||
PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername());
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
// The player was disconnected
|
||||
return;
|
||||
}
|
||||
|
||||
PreLoginComponentResult result = event.getResult();
|
||||
Optional<Component> disconnectReason = result.getReasonComponent();
|
||||
if (disconnectReason.isPresent()) {
|
||||
// The component is guaranteed to be provided if the connection was denied.
|
||||
inbound.disconnect(disconnectReason.get());
|
||||
return;
|
||||
}
|
||||
|
||||
inbound.loginEventFired(() -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
// The player was disconnected
|
||||
return;
|
||||
}
|
||||
|
||||
mcConnection.eventLoop().execute(() -> {
|
||||
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode()
|
||||
|| result.isOnlineModeAllowed())) {
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateEncryptionRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
mcConnection.write(request);
|
||||
this.currentState = LoginState.ENCRYPTION_REQUEST_SENT;
|
||||
} else {
|
||||
mcConnection.setSessionHandler(new AuthSessionHandler(
|
||||
server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
}, mcConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception in pre-login stage", ex);
|
||||
return null;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -105,6 +133,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(EncryptionResponse packet) {
|
||||
assertState(LoginState.ENCRYPTION_REQUEST_SENT);
|
||||
this.currentState = LoginState.ENCRYPTION_RESPONSE_RECEIVED;
|
||||
|
||||
ServerLogin login = this.login;
|
||||
if (login == null) {
|
||||
throw new IllegalStateException("No ServerLogin packet received yet.");
|
||||
@ -156,8 +187,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
Response profileResponse = hasJoinedResponse.get();
|
||||
if (profileResponse.getStatusCode() == 200) {
|
||||
// All went well, initialize the session.
|
||||
initializePlayer(GENERAL_GSON.fromJson(profileResponse.getResponseBody(),
|
||||
GameProfile.class), true);
|
||||
mcConnection.setSessionHandler(new AuthSessionHandler(
|
||||
server, inbound, GENERAL_GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class), true
|
||||
));
|
||||
} else if (profileResponse.getStatusCode() == 204) {
|
||||
// Apparently an offline-mode user logged onto this online-mode proxy.
|
||||
inbound.disconnect(Component.translatable("velocity.error.online-mode-only",
|
||||
@ -184,52 +216,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void beginPreLogin() {
|
||||
ServerLogin login = this.login;
|
||||
if (login == null) {
|
||||
throw new IllegalStateException("No ServerLogin packet received yet.");
|
||||
}
|
||||
PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername());
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
// The player was disconnected
|
||||
return;
|
||||
}
|
||||
|
||||
PreLoginComponentResult result = event.getResult();
|
||||
Optional<Component> disconnectReason = result.getReasonComponent();
|
||||
if (disconnectReason.isPresent()) {
|
||||
// The component is guaranteed to be provided if the connection was denied.
|
||||
inbound.disconnect(disconnectReason.get());
|
||||
return;
|
||||
}
|
||||
|
||||
inbound.loginEventFired(() -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
// The player was disconnected
|
||||
return;
|
||||
}
|
||||
|
||||
mcConnection.eventLoop().execute(() -> {
|
||||
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode()
|
||||
|| result.isOnlineModeAllowed())) {
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateEncryptionRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
mcConnection.write(request);
|
||||
} else {
|
||||
initializePlayer(GameProfile.forOfflinePlayer(login.getUsername()), false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, mcConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception in pre-login stage", ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private EncryptionRequest generateEncryptionRequest() {
|
||||
byte[] verify = new byte[4];
|
||||
ThreadLocalRandom.current().nextBytes(verify);
|
||||
@ -240,127 +226,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
return request;
|
||||
}
|
||||
|
||||
private void initializePlayer(GameProfile profile, boolean onlineMode) {
|
||||
// Some connection types may need to alter the game profile.
|
||||
profile = mcConnection.getType().addGameProfileTokensIfRequired(profile,
|
||||
server.getConfiguration().getPlayerInfoForwardingMode());
|
||||
GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(inbound, profile,
|
||||
onlineMode);
|
||||
final GameProfile finalProfile = profile;
|
||||
|
||||
server.getEventManager().fire(profileRequestEvent).thenComposeAsync(profileEvent -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
// The player disconnected after we authenticated them.
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
|
||||
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode);
|
||||
this.connectedPlayer = player;
|
||||
if (!server.canRegisterConnection(player)) {
|
||||
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy",
|
||||
NamedTextColor.RED), true);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
|
||||
return server.getEventManager()
|
||||
.fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (!mcConnection.isClosed()) {
|
||||
// wait for permissions to load, then set the players permission function
|
||||
final PermissionFunction function = event.createFunction(player);
|
||||
if (function == null) {
|
||||
logger.error(
|
||||
"A plugin permission provider {} provided an invalid permission function"
|
||||
+ " for player {}. This is a bug in the plugin, not in Velocity. Falling"
|
||||
+ " back to the default permission function.",
|
||||
event.getProvider().getClass().getName(),
|
||||
player.getUsername());
|
||||
} else {
|
||||
player.setPermissionFunction(function);
|
||||
}
|
||||
completeLoginProtocolPhaseAndInitialize(player);
|
||||
}
|
||||
}, mcConnection.eventLoop());
|
||||
}, mcConnection.eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception during connection of {}", finalProfile, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) {
|
||||
int threshold = server.getConfiguration().getCompressionThreshold();
|
||||
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
||||
mcConnection.write(new SetCompression(threshold));
|
||||
mcConnection.setCompressionThreshold(threshold);
|
||||
}
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
UUID playerUniqueId = player.getUniqueId();
|
||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) {
|
||||
playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername());
|
||||
}
|
||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||
success.setUsername(player.getUsername());
|
||||
success.setUuid(playerUniqueId);
|
||||
mcConnection.write(success);
|
||||
|
||||
mcConnection.setAssociation(player);
|
||||
mcConnection.setState(StateRegistry.PLAY);
|
||||
|
||||
server.getEventManager().fire(new LoginEvent(player))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (mcConnection.isClosed()) {
|
||||
// The player was disconnected
|
||||
server.getEventManager().fireAndForget(new DisconnectEvent(player,
|
||||
LoginStatus.CANCELLED_BY_USER_BEFORE_COMPLETE));
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Component> reason = event.getResult().getReasonComponent();
|
||||
if (reason.isPresent()) {
|
||||
player.disconnect0(reason.get(), true);
|
||||
} else {
|
||||
if (!server.registerConnection(player)) {
|
||||
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"),
|
||||
true);
|
||||
return;
|
||||
}
|
||||
|
||||
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
server.getEventManager().fire(new PostLoginEvent(player))
|
||||
.thenCompose((ignored) -> connectToInitialServer(player))
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while connecting {} to initial server", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}, mcConnection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while completing login initialisation phase for {}", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> connectToInitialServer(ConnectedPlayer player) {
|
||||
Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
|
||||
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player,
|
||||
initialFromConfig.orElse(null));
|
||||
|
||||
return server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
Optional<RegisteredServer> toTry = event.getInitialServer();
|
||||
if (!toTry.isPresent()) {
|
||||
player.disconnect0(Component.translatable("velocity.error.no-available-servers",
|
||||
NamedTextColor.RED), true);
|
||||
return;
|
||||
}
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
}, mcConnection.eventLoop());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
mcConnection.close(true);
|
||||
@ -368,9 +233,23 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
if (connectedPlayer != null) {
|
||||
connectedPlayer.teardown();
|
||||
}
|
||||
this.inbound.cleanup();
|
||||
}
|
||||
|
||||
private void assertState(LoginState expectedState) {
|
||||
if (this.currentState != expectedState) {
|
||||
if (MinecraftDecoder.DEBUG) {
|
||||
logger.error("{} Received an unexpected packet requiring state {}, but we are in {}", inbound,
|
||||
expectedState, this.currentState);
|
||||
}
|
||||
mcConnection.close(true);
|
||||
}
|
||||
}
|
||||
|
||||
private enum LoginState {
|
||||
LOGIN_PACKET_EXPECTED,
|
||||
LOGIN_PACKET_RECEIVED,
|
||||
ENCRYPTION_REQUEST_SENT,
|
||||
ENCRYPTION_RESPONSE_RECEIVED
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ package com.velocitypowered.proxy.connection.client;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.LoginPhaseConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
@ -144,4 +145,8 @@ public class LoginInboundConnection implements LoginPhaseConnection {
|
||||
onAllMessagesHandled.run();
|
||||
}
|
||||
}
|
||||
|
||||
MinecraftConnection delegatedConnection() {
|
||||
return delegate.getConnection();
|
||||
}
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren