From d2b8271eb4b23578d833058af4e2fb1cbff4ba94 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 16 Nov 2019 23:17:09 -0500 Subject: [PATCH] Allow running Velocity without any servers. This is a niche setup, however if your network is 100% dynamically configured, this is a handy feature to have available. To support this functionality, a new PlayerChooseInitialServerEvent event was added to allow the initial server to connect to be changed as desired. --- .../PlayerChooseInitialServerEvent.java | 51 ++++++++++++++++++ .../proxy/config/VelocityConfiguration.java | 54 +++++++++---------- .../connection/client/ConnectedPlayer.java | 37 +++++-------- .../client/LoginSessionHandler.java | 35 ++++++------ 4 files changed, 106 insertions(+), 71 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/PlayerChooseInitialServerEvent.java diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerChooseInitialServerEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChooseInitialServerEvent.java new file mode 100644 index 000000000..3ff289363 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChooseInitialServerEvent.java @@ -0,0 +1,51 @@ +package com.velocitypowered.api.event.player; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import java.util.Optional; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Fired when a player has finished connecting to the proxy and we need to choose the first server + * to connect to. + */ +public class PlayerChooseInitialServerEvent { + + private final Player player; + private @Nullable RegisteredServer initialServer; + + /** + * Constructs a PlayerChooseInitialServerEvent. + * @param player the player that was connected + * @param initialServer the initial server selected, may be {@code null} + */ + public PlayerChooseInitialServerEvent(Player player, @Nullable RegisteredServer initialServer) { + this.player = Preconditions.checkNotNull(player, "player"); + this.initialServer = initialServer; + } + + public Player getPlayer() { + return player; + } + + public Optional getInitialServer() { + return Optional.ofNullable(initialServer); + } + + /** + * Sets the new initial server. + * @param server the initial server the player should connect to + */ + public void setInitialServer(RegisteredServer server) { + this.initialServer = server; + } + + @Override + public String toString() { + return "PlayerChooseInitialServerEvent{" + + "player=" + player + + ", initialServer=" + initialServer + + '}'; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 632a0a549..467363daf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -192,44 +192,38 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi } if (servers.getServers().isEmpty()) { - logger.error("You have no servers configured. :("); - valid = false; - } else { - if (servers.getAttemptConnectionOrder().isEmpty()) { - logger.error("No fallback servers are configured!"); + logger.warn("You don't have any servers configured."); + } + + for (Map.Entry entry : servers.getServers().entrySet()) { + try { + AddressUtil.parseAddress(entry.getValue()); + } catch (IllegalArgumentException e) { + logger.error("Server {} does not have a valid IP address.", entry.getKey(), e); valid = false; } + } - for (Map.Entry entry : servers.getServers().entrySet()) { - try { - AddressUtil.parseAddress(entry.getValue()); - } catch (IllegalArgumentException e) { - logger.error("Server {} does not have a valid IP address.", entry.getKey(), e); - valid = false; - } + for (String s : servers.getAttemptConnectionOrder()) { + if (!servers.getServers().containsKey(s)) { + logger.error("Fallback server " + s + " is not registered in your configuration!"); + valid = false; + } + } + + for (Map.Entry> entry : forcedHosts.getForcedHosts().entrySet()) { + if (entry.getValue().isEmpty()) { + logger.error("Forced host '{}' does not contain any servers", entry.getKey()); + valid = false; + continue; } - for (String s : servers.getAttemptConnectionOrder()) { - if (!servers.getServers().containsKey(s)) { - logger.error("Fallback server " + s + " is not registered in your configuration!"); + for (String server : entry.getValue()) { + if (!servers.getServers().containsKey(server)) { + logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey()); valid = false; } } - - for (Map.Entry> entry : forcedHosts.getForcedHosts().entrySet()) { - if (entry.getValue().isEmpty()) { - logger.error("Forced host '{}' does not contain any servers", entry.getKey()); - valid = false; - continue; - } - - for (String server : entry.getValue()) { - if (!servers.getServers().containsKey(server)) { - logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey()); - valid = false; - } - } - } } try { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index e7be50b36..1145e905b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -417,33 +417,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return; } - if (connectedServer == null) { - Optional nextServer = getNextServerToTry(rs); - if (nextServer.isPresent()) { - // There can't be any connection in flight now. - resetInFlightConnection(); - createConnectionRequest(nextServer.get()).fireAndForget(); - } else { - disconnect(friendlyReason); - } + boolean kickedFromCurrent = connectedServer == null || connectedServer.getServer().equals(rs); + ServerKickResult result; + if (kickedFromCurrent) { + Optional next = getNextServerToTry(rs); + result = next.map(RedirectPlayer::create) + .orElseGet(() -> DisconnectPlayer.create(friendlyReason)); } else { - boolean kickedFromCurrent = connectedServer.getServer().equals(rs); - ServerKickResult result; - if (kickedFromCurrent) { - Optional next = getNextServerToTry(rs); - result = next.map(RedirectPlayer::create) - .orElseGet(() -> DisconnectPlayer.create(friendlyReason)); - } else { - // If we were kicked by going to another server, the connection should not be in flight - if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) { - resetInFlightConnection(); - } - result = Notify.create(friendlyReason); + // If we were kicked by going to another server, the connection should not be in flight + if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) { + resetInFlightConnection(); } - KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason, - !kickedFromCurrent, result); - handleKickEvent(originalEvent, friendlyReason); + result = Notify.create(friendlyReason); } + KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason, + !kickedFromCurrent, result); + handleKickEvent(originalEvent, friendlyReason); } private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index b2c47847f..6fb58ffea 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -1,24 +1,21 @@ package com.velocitypowered.proxy.connection.client; import static com.google.common.net.UrlEscapers.urlFormParameterEscaper; -import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.proxy.VelocityServer.GSON; import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; -import static com.velocitypowered.proxy.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL; import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa; import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId; import static org.asynchttpclient.Dsl.asyncHttpClient; -import static org.asynchttpclient.Dsl.config; import com.google.common.base.Preconditions; -import com.google.common.net.UrlEscapers; 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.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.VelocityServer; @@ -29,17 +26,12 @@ import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; -import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; -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 com.velocitypowered.proxy.util.VelocityMessages; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Arrays; @@ -50,7 +42,6 @@ import java.util.concurrent.ThreadLocalRandom; import net.kyori.text.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.asynchttpclient.Dsl; import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.Response; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -231,12 +222,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } private void finishLogin(ConnectedPlayer player) { - Optional toTry = player.getNextServerToTry(); - if (!toTry.isPresent()) { - player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS); - return; - } - int threshold = server.getConfiguration().getCompressionThreshold(); if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { mcConnection.write(new SetCompression(threshold)); @@ -269,11 +254,27 @@ public class LoginSessionHandler implements MinecraftSessionHandler { mcConnection.setSessionHandler(new InitialConnectSessionHandler(player)); server.getEventManager().fire(new PostLoginEvent(player)) - .thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget()); + .thenRun(() -> connectToInitialServer(player)); } }, mcConnection.eventLoop()); } + private void connectToInitialServer(ConnectedPlayer player) { + Optional initialFromConfig = player.getNextServerToTry(); + PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player, + initialFromConfig.orElse(null)); + + server.getEventManager().fire(event) + .thenRunAsync(() -> { + Optional toTry = event.getInitialServer(); + if (!toTry.isPresent()) { + player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS); + return; + } + player.createConnectionRequest(toTry.get()).fireAndForget(); + }, mcConnection.eventLoop()); + } + @Override public void handleUnknown(ByteBuf buf) { mcConnection.close();