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.natives.util.Natives;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
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.connection.client.StatusSessionHandler;
|
||||||
import com.velocitypowered.proxy.network.Connections;
|
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.VelocityConnectionEvent;
|
import com.velocitypowered.proxy.protocol.VelocityConnectionEvent;
|
||||||
@ -178,7 +177,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
if (cause instanceof ReadTimeoutException) {
|
if (cause instanceof ReadTimeoutException) {
|
||||||
logger.error("{}: read timed out", association);
|
logger.error("{}: read timed out", association);
|
||||||
} else {
|
} else {
|
||||||
boolean frontlineHandler = sessionHandler instanceof LoginSessionHandler
|
boolean frontlineHandler = sessionHandler instanceof InitialLoginSessionHandler
|
||||||
|| sessionHandler instanceof HandshakeSessionHandler
|
|| sessionHandler instanceof HandshakeSessionHandler
|
||||||
|| sessionHandler instanceof StatusSessionHandler;
|
|| sessionHandler instanceof StatusSessionHandler;
|
||||||
boolean isQuietDecoderException = cause instanceof QuietDecoderException;
|
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.net.InetSocketAddress;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import net.kyori.adventure.text.Component;
|
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 net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -144,7 +142,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
LoginInboundConnection lic = new LoginInboundConnection(ic);
|
LoginInboundConnection lic = new LoginInboundConnection(ic);
|
||||||
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic));
|
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) {
|
private ConnectionType getHandshakeConnectionType(Handshake handshake) {
|
||||||
|
@ -18,38 +18,23 @@
|
|||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
|
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.VelocityServer.GENERAL_GSON;
|
||||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
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.decryptRsa;
|
||||||
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
|
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
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;
|
||||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
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.GameProfile;
|
||||||
import com.velocitypowered.api.util.UuidUtils;
|
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
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.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
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.EncryptionRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
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 io.netty.buffer.ByteBuf;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
@ -57,8 +42,6 @@ import java.security.KeyPair;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@ -69,9 +52,9 @@ import org.asynchttpclient.ListenableFuture;
|
|||||||
import org.asynchttpclient.Response;
|
import org.asynchttpclient.Response;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
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 =
|
private static final String MOJANG_HASJOINED_URL =
|
||||||
System.getProperty("mojang.sessionserver", "https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
System.getProperty("mojang.sessionserver", "https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
||||||
.concat("?username=%s&serverId=%s");
|
.concat("?username=%s&serverId=%s");
|
||||||
@ -81,10 +64,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
private final LoginInboundConnection inbound;
|
private final LoginInboundConnection inbound;
|
||||||
private @MonotonicNonNull ServerLogin login;
|
private @MonotonicNonNull ServerLogin login;
|
||||||
private byte[] verify = EMPTY_BYTE_ARRAY;
|
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) {
|
LoginInboundConnection inbound) {
|
||||||
this.server = Preconditions.checkNotNull(server, "server");
|
this.server = Preconditions.checkNotNull(server, "server");
|
||||||
this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection");
|
this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection");
|
||||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||||
@ -92,8 +75,53 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(ServerLogin packet) {
|
public boolean handle(ServerLogin packet) {
|
||||||
|
assertState(LoginState.LOGIN_PACKET_EXPECTED);
|
||||||
|
this.currentState = LoginState.LOGIN_PACKET_RECEIVED;
|
||||||
this.login = packet;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +133,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(EncryptionResponse packet) {
|
public boolean handle(EncryptionResponse packet) {
|
||||||
|
assertState(LoginState.ENCRYPTION_REQUEST_SENT);
|
||||||
|
this.currentState = LoginState.ENCRYPTION_RESPONSE_RECEIVED;
|
||||||
|
|
||||||
ServerLogin login = this.login;
|
ServerLogin login = this.login;
|
||||||
if (login == null) {
|
if (login == null) {
|
||||||
throw new IllegalStateException("No ServerLogin packet received yet.");
|
throw new IllegalStateException("No ServerLogin packet received yet.");
|
||||||
@ -156,8 +187,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
Response profileResponse = hasJoinedResponse.get();
|
Response profileResponse = hasJoinedResponse.get();
|
||||||
if (profileResponse.getStatusCode() == 200) {
|
if (profileResponse.getStatusCode() == 200) {
|
||||||
// All went well, initialize the session.
|
// All went well, initialize the session.
|
||||||
initializePlayer(GENERAL_GSON.fromJson(profileResponse.getResponseBody(),
|
mcConnection.setSessionHandler(new AuthSessionHandler(
|
||||||
GameProfile.class), true);
|
server, inbound, GENERAL_GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class), true
|
||||||
|
));
|
||||||
} else if (profileResponse.getStatusCode() == 204) {
|
} else if (profileResponse.getStatusCode() == 204) {
|
||||||
// Apparently an offline-mode user logged onto this online-mode proxy.
|
// Apparently an offline-mode user logged onto this online-mode proxy.
|
||||||
inbound.disconnect(Component.translatable("velocity.error.online-mode-only",
|
inbound.disconnect(Component.translatable("velocity.error.online-mode-only",
|
||||||
@ -184,52 +216,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
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() {
|
private EncryptionRequest generateEncryptionRequest() {
|
||||||
byte[] verify = new byte[4];
|
byte[] verify = new byte[4];
|
||||||
ThreadLocalRandom.current().nextBytes(verify);
|
ThreadLocalRandom.current().nextBytes(verify);
|
||||||
@ -240,127 +226,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
return request;
|
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
|
@Override
|
||||||
public void handleUnknown(ByteBuf buf) {
|
public void handleUnknown(ByteBuf buf) {
|
||||||
mcConnection.close(true);
|
mcConnection.close(true);
|
||||||
@ -368,9 +233,23 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnected() {
|
public void disconnected() {
|
||||||
if (connectedPlayer != null) {
|
|
||||||
connectedPlayer.teardown();
|
|
||||||
}
|
|
||||||
this.inbound.cleanup();
|
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.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.LoginPhaseConnection;
|
import com.velocitypowered.api.proxy.LoginPhaseConnection;
|
||||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
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.LoginPluginMessage;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
@ -144,4 +145,8 @@ public class LoginInboundConnection implements LoginPhaseConnection {
|
|||||||
onAllMessagesHandled.run();
|
onAllMessagesHandled.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MinecraftConnection delegatedConnection() {
|
||||||
|
return delegate.getConnection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren