From aa38d3e561862d92c1a62f296cfdb6cdf983871a Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 23 Apr 2022 18:07:34 -0400 Subject: [PATCH] Implement a proper login state machine for logins Fixes #688 and probably a host of other potential problems. --- .../proxy/connection/MinecraftConnection.java | 5 +- .../connection/client/AuthSessionHandler.java | 205 +++++++++++++ .../client/HandshakeSessionHandler.java | 4 +- ...r.java => InitialLoginSessionHandler.java} | 271 +++++------------- .../client/LoginInboundConnection.java | 5 + 5 files changed, 288 insertions(+), 202 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java rename proxy/src/main/java/com/velocitypowered/proxy/connection/client/{LoginSessionHandler.java => InitialLoginSessionHandler.java} (54%) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index 2d9e5004e..7d08a3037 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java new file mode 100644 index 000000000..4ba401f96 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -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 . + */ + +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 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 connectToInitialServer(ConnectedPlayer player) { + Optional initialFromConfig = player.getNextServerToTry(); + PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player, + initialFromConfig.orElse(null)); + + return server.getEventManager().fire(event) + .thenRunAsync(() -> { + Optional 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(); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java index 74e7ad080..2870b64bc 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java @@ -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) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java similarity index 54% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index 4ab7fe628..cd20fb2a9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -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,10 +64,10 @@ 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, - LoginInboundConnection inbound) { + InitialLoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection, + LoginInboundConnection inbound) { this.server = Preconditions.checkNotNull(server, "server"); this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection"); this.inbound = Preconditions.checkNotNull(inbound, "inbound"); @@ -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 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 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 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 connectToInitialServer(ConnectedPlayer player) { - Optional initialFromConfig = player.getNextServerToTry(); - PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player, - initialFromConfig.orElse(null)); - - return server.getEventManager().fire(event) - .thenRunAsync(() -> { - Optional 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 + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginInboundConnection.java index a9efb8d3d..f707c4cc7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginInboundConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginInboundConnection.java @@ -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(); + } }