diff --git a/api/src/main/java/com/velocitypowered/api/event/player/KickedFromServerEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/KickedFromServerEvent.java index 04359eea8..86bce5e66 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/KickedFromServerEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/KickedFromServerEvent.java @@ -3,6 +3,7 @@ package com.velocitypowered.api.event.player; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; @@ -13,12 +14,12 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ public class KickedFromServerEvent implements ResultedEvent { private final Player player; - private final ServerInfo server; + private final RegisteredServer server; private final Component originalReason; private final boolean duringLogin; private ServerKickResult result; - public KickedFromServerEvent(Player player, ServerInfo server, Component originalReason, boolean duringLogin, Component fancyReason) { + public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, boolean duringLogin, Component fancyReason) { this.player = Preconditions.checkNotNull(player, "player"); this.server = Preconditions.checkNotNull(server, "server"); this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason"); @@ -40,7 +41,7 @@ public class KickedFromServerEvent implements ResultedEvent { private final Player player; - private final ServerInfo originalServer; + private final RegisteredServer originalServer; private ServerResult result; - public ServerPreConnectEvent(Player player, ServerInfo originalServer) { + public ServerPreConnectEvent(Player player, RegisteredServer originalServer) { this.player = Preconditions.checkNotNull(player, "player"); this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer"); this.result = ServerResult.allowed(originalServer); @@ -37,7 +38,7 @@ public class ServerPreConnectEvent implements ResultedEvent getServer() { + public Optional getServer() { return Optional.ofNullable(server); } @@ -78,14 +79,14 @@ public class ServerPreConnectEvent implements ResultedEvent connect(); + CompletableFuture connect(); /** * Initiates the connection to the remote server without waiting for a result. Velocity will use generic error diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 0ebb1fdfa..83c05a4ae 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -4,6 +4,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.util.MessagePosition; import net.kyori.text.Component; @@ -59,10 +60,10 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage /** * Creates a new connection request so that the player can connect to another server. - * @param info the server to connect to + * @param server the server to connect to * @return a new connection request */ - ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info); + ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server); /** * Sets the tab list header and footer for the player. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java index b4c851c58..eb2edcbe1 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -5,6 +5,7 @@ import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.proxy.messages.ChannelRegistrar; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.proxy.server.ServerInfo; @@ -45,23 +46,24 @@ public interface ProxyServer { int getPlayerCount(); /** - * Retrieves a registered {@link ServerInfo} instance by its name. The search is case-insensitive. + * Retrieves a registered {@link RegisteredServer} instance by its name. The search is case-insensitive. * @param name the name of the server * @return the registered server, which may be empty */ - Optional getServerInfo(String name); + Optional getServerInfo(String name); /** - * Retrieves all {@link ServerInfo}s registered with this proxy. + * Retrieves all {@link RegisteredServer}s registered with this proxy. * @return the servers registered with this proxy */ - Collection getAllServers(); + Collection getAllServers(); /** * Registers a server with this proxy. A server with this name should not already exist. * @param server the server to register + * @return the newly registered server */ - void registerServer(ServerInfo server); + RegisteredServer registerServer(ServerInfo server); /** * Unregisters this server from the proxy. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java index 5f12fb326..b800131c0 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java @@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; /** @@ -12,6 +13,12 @@ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSi * Returns the server that this connection is connected to. * @return the server this connection is connected to */ + RegisteredServer getServer(); + + /** + * Returns the server info for this connection. + * @return the server info for this connection + */ ServerInfo getServerInfo(); /** diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java index 57ebe4ea6..37f02e539 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java @@ -8,6 +8,7 @@ public interface ChannelMessageSink { * Sends a plugin message to this target. * @param identifier the channel identifier to send the message on * @param data the data to send + * @return whether or not the message could be sent */ - void sendPluginMessage(ChannelIdentifier identifier, byte[] data); + boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java b/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java new file mode 100644 index 000000000..b7bf9acb5 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java @@ -0,0 +1,30 @@ +package com.velocitypowered.api.proxy.server; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.messages.ChannelMessageSink; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +/** + * Represents a server that has been registered with the proxy. + */ +public interface RegisteredServer extends ChannelMessageSink { + /** + * Returns the {@link ServerInfo} for this server. + * @return the server info + */ + ServerInfo getServerInfo(); + + /** + * Returns a list of all the players currently connected to this server on this proxy. + * @return the players on this proxy + */ + Collection getPlayersConnected(); + + /** + * Attempts to ping the remote server and return the server list ping result. + * @return the server ping result from the server + */ + CompletableFuture ping(); +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index d23709111..55a2ba4c7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -10,6 +10,7 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.proxy.server.ServerInfo; @@ -30,7 +31,7 @@ import com.velocitypowered.proxy.scheduler.VelocityScheduler; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.Ratelimiter; -import com.velocitypowered.proxy.util.ServerMap; +import com.velocitypowered.proxy.server.ServerMap; import io.netty.bootstrap.Bootstrap; import net.kyori.text.Component; import net.kyori.text.TextComponent; @@ -61,7 +62,7 @@ public class VelocityServer implements ProxyServer { private VelocityConfiguration configuration; private NettyHttpClient httpClient; private KeyPair serverKeyPair; - private final ServerMap servers = new ServerMap(); + private final ServerMap servers = new ServerMap(this); private final VelocityCommandManager commandManager = new VelocityCommandManager(); private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); private boolean shutdown = false; @@ -263,19 +264,19 @@ public class VelocityServer implements ProxyServer { } @Override - public Optional getServerInfo(String name) { + public Optional getServerInfo(String name) { Preconditions.checkNotNull(name, "name"); return servers.getServer(name); } @Override - public Collection getAllServers() { + public Collection getAllServers() { return servers.getAllServers(); } @Override - public void registerServer(ServerInfo server) { - servers.register(server); + public RegisteredServer registerServer(ServerInfo server) { + return servers.register(server); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java index b42a9ed26..3707a800c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -6,6 +6,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; @@ -34,7 +35,7 @@ public class ServerCommand implements Command { if (args.length == 1) { // Trying to connect to a server. String serverName = args[0]; - Optional toConnect = server.getServerInfo(serverName); + Optional toConnect = server.getServerInfo(serverName); if (!toConnect.isPresent()) { player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED)); return; @@ -48,17 +49,19 @@ public class ServerCommand implements Command { // Assemble the list of servers as components TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW); - List infos = ImmutableList.copyOf(server.getAllServers()); + List infos = ImmutableList.copyOf(server.getAllServers()); for (int i = 0; i < infos.size(); i++) { - ServerInfo serverInfo = infos.get(i); - TextComponent infoComponent = TextComponent.of(serverInfo.getName()); - if (serverInfo.getName().equals(currentServer)) { + RegisteredServer rs = infos.get(i); + TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName()); + String playersText = rs.getPlayersConnected().size() + " player(s) online"; + if (rs.getServerInfo().getName().equals(currentServer)) { infoComponent = infoComponent.color(TextColor.GREEN) - .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Currently connected to this server"))); + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + TextComponent.of("Currently connected to this server\n" + playersText))); } else { infoComponent = infoComponent.color(TextColor.GRAY) - .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + serverInfo.getName())) - .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server"))); + .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName())) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText))); } serverListBuilder.append(infoComponent); if (i != infos.size() - 1) { @@ -74,11 +77,11 @@ public class ServerCommand implements Command { public List suggest(CommandSource source, String[] currentArgs) { if (currentArgs.length == 0) { return server.getAllServers().stream() - .map(ServerInfo::getName) + .map(rs -> rs.getServerInfo().getName()) .collect(Collectors.toList()); } else if (currentArgs.length == 1) { return server.getAllServers().stream() - .map(ServerInfo::getName) + .map(rs -> rs.getServerInfo().getName()) .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) .collect(Collectors.toList()); } else { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index e62eb4615..fb7e1138d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -25,7 +25,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void activated() { - server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServerInfo())); + server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServer())); + connection.getServer().addPlayer(connection.getPlayer()); } @Override @@ -46,7 +47,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { } else if (packet instanceof Disconnect) { Disconnect original = (Disconnect) packet; connection.disconnect(); - connection.getPlayer().handleConnectionException(connection.getServerInfo(), original); + connection.getPlayer().handleConnectionException(connection.getServer(), original); } else if (packet instanceof JoinGame) { playerHandler.handleBackendJoinGame((JoinGame) packet); } else if (packet instanceof BossBar) { @@ -112,7 +113,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void exception(Throwable throwable) { - connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable); + connection.getPlayer().handleConnectionException(connection.getServer(), throwable); } public VelocityServer getServer() { @@ -121,10 +122,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void disconnected() { - if (connection.isGracefulDisconnect()) { - return; + connection.getServer().removePlayer(connection.getPlayer()); + if (!connection.isGracefulDisconnect()) { + connection.getPlayer().handleConnectionException(connection.getServer(), Disconnect.create( + ConnectionMessages.UNEXPECTED_DISCONNECT)); } - connection.getPlayer().handleConnectionException(connection.getServerInfo(), Disconnect.create(ConnectionMessages.UNEXPECTED_DISCONNECT)); } private boolean canForwardPluginMessage(PluginMessage message) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index 496a4a686..87f55b6e9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -4,6 +4,7 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -19,6 +20,7 @@ import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.server.VelocityRegisteredServer; import io.netty.channel.*; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.util.AttributeKey; @@ -38,7 +40,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, static final AttributeKey> CONNECTION_NOTIFIER = AttributeKey.newInstance("connection-notification-result"); - private final ServerInfo serverInfo; + private final VelocityRegisteredServer registeredServer; private final ConnectedPlayer proxyPlayer; private final VelocityServer server; private MinecraftConnection minecraftConnection; @@ -46,8 +48,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, private boolean hasCompletedJoin = false; private boolean gracefulDisconnect = false; - public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) { - this.serverInfo = target; + public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) { + this.registeredServer = registeredServer; this.proxyPlayer = proxyPlayer; this.server = server; } @@ -72,7 +74,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, ch.pipeline().addLast(HANDLER, connection); } }) - .connect(serverInfo.getAddress()) + .connect(registeredServer.getServerInfo().getAddress()) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { @@ -94,7 +96,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, // separated by \0 (the null byte). In order, you send the original host, the player's IP, their // UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang). - return serverInfo.getAddress().getHostString() + "\0" + + return registeredServer.getServerInfo().getAddress().getHostString() + "\0" + proxyPlayer.getRemoteAddress().getHostString() + "\0" + proxyPlayer.getProfile().getId() + "\0" + GSON.toJson(proxyPlayer.getProfile().getProperties()); @@ -112,9 +114,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, } else if (proxyPlayer.getConnection().isLegacyForge()) { handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0"); } else { - handshake.setServerAddress(serverInfo.getAddress().getHostString()); + handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString()); } - handshake.setPort(serverInfo.getAddress().getPort()); + handshake.setPort(registeredServer.getServerInfo().getAddress().getPort()); minecraftConnection.write(handshake); int protocolVersion = proxyPlayer.getConnection().getProtocolVersion(); @@ -136,8 +138,14 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, return minecraftConnection; } + @Override + public VelocityRegisteredServer getServer() { + return registeredServer; + } + + @Override public ServerInfo getServerInfo() { - return serverInfo; + return registeredServer.getServerInfo(); } @Override @@ -155,17 +163,18 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, @Override public String toString() { - return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName(); + return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName(); } @Override - public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(data, "data"); PluginMessage message = new PluginMessage(); message.setChannel(identifier.getId()); message.setData(data); minecraftConnection.write(message); + return true; } public boolean isLegacyForge() { 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 b56b74753..5cfe49ac4 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 @@ -11,6 +11,7 @@ import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.proxy.VelocityServer; @@ -24,6 +25,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.util.ThrowableUtils; import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.proxy.protocol.packet.Disconnect; @@ -159,8 +161,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } @Override - public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) { - return new ConnectionRequestBuilderImpl(info); + public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) { + return new ConnectionRequestBuilderImpl(server); } @Override @@ -184,57 +186,57 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return connectedServer; } - public void handleConnectionException(ServerInfo info, Throwable throwable) { + public void handleConnectionException(RegisteredServer server, Throwable throwable) { String error = ThrowableUtils.briefDescription(throwable); String userMessage; - if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { - userMessage = "Exception in server " + info.getName(); + if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { + userMessage = "Exception in server " + server.getServerInfo().getName(); } else { - logger.error("{}: unable to connect to server {}", this, info.getName(), throwable); - userMessage = "Exception connecting to server " + info.getName(); + logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), throwable); + userMessage = "Exception connecting to server " + server.getServerInfo().getName(); } - handleConnectionException(info, null, TextComponent.builder() + handleConnectionException(server, null, TextComponent.builder() .content(userMessage + ": ") .color(TextColor.RED) .append(TextComponent.of(error, TextColor.WHITE)) .build()); } - public void handleConnectionException(ServerInfo info, Disconnect disconnect) { + public void handleConnectionException(RegisteredServer server, Disconnect disconnect) { Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason()); String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); - if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { - logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason); - handleConnectionException(info, disconnectReason, TextComponent.builder() - .content("Kicked from " + info.getName() + ": ") + if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { + logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), plainTextReason); + handleConnectionException(server, disconnectReason, TextComponent.builder() + .content("Kicked from " + server.getServerInfo().getName() + ": ") .color(TextColor.RED) .append(disconnectReason) .build()); } else { - logger.error("{}: disconnected while connecting to {}: {}", this, info.getName(), plainTextReason); - handleConnectionException(info, disconnectReason, TextComponent.builder() - .content("Unable to connect to " + info.getName() + ": ") + logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason); + handleConnectionException(server, disconnectReason, TextComponent.builder() + .content("Unable to connect to " + server.getServerInfo().getName() + ": ") .color(TextColor.RED) .append(disconnectReason) .build()); } } - private void handleConnectionException(ServerInfo info, @Nullable Component kickReason, Component friendlyReason) { - boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);; + private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) { + boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo()); connectionInFlight = null; if (connectedServer == null) { // The player isn't yet connected to a server. - Optional nextServer = getNextServerToTry(); + Optional nextServer = getNextServerToTry(); if (nextServer.isPresent()) { createConnectionRequest(nextServer.get()).fireAndForget(); } else { connection.closeWith(Disconnect.create(friendlyReason)); } - } else if (connectedServer.getServerInfo().equals(info)) { + } else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) { // Already connected to the server being disconnected from. if (kickReason != null) { - server.getEventManager().fire(new KickedFromServerEvent(this, info, kickReason, !alreadyConnected, friendlyReason)) + server.getEventManager().fire(new KickedFromServerEvent(this, rs, kickReason, !alreadyConnected, friendlyReason)) .thenAcceptAsync(event -> { if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) { KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult(); @@ -255,7 +257,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } } - Optional getNextServerToTry() { + Optional getNextServerToTry() { List serversToTry = server.getConfiguration().getAttemptConnectionOrder(); if (tryIndex >= serversToTry.size()) { return Optional.empty(); @@ -289,7 +291,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { ); } - return new VelocityServerConnection(newEvent.getResult().getServer().get(), this, server).connect(); + RegisteredServer rs = newEvent.getResult().getServer().get(); + Preconditions.checkState(rs instanceof VelocityRegisteredServer, "Not a valid Velocity server."); + return new VelocityServerConnection((VelocityRegisteredServer) rs, this, server).connect(); }); } @@ -335,25 +339,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } @Override - public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(data, "data"); PluginMessage message = new PluginMessage(); message.setChannel(identifier.getId()); message.setData(data); connection.write(message); + return true; } private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { - private final ServerInfo info; + private final RegisteredServer server; - ConnectionRequestBuilderImpl(ServerInfo info) { - this.info = Preconditions.checkNotNull(info, "info"); + ConnectionRequestBuilderImpl(RegisteredServer server) { + this.server = Preconditions.checkNotNull(server, "info"); } @Override - public ServerInfo getServer() { - return info; + public RegisteredServer getServer() { + return server; } @Override @@ -366,7 +371,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { connect() .whenCompleteAsync((status, throwable) -> { if (throwable != null) { - handleConnectionException(info, throwable); + handleConnectionException(server, throwable); return; } @@ -381,7 +386,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { // Ignored; the plugin probably already handled this. break; case SERVER_DISCONNECTED: - handleConnectionException(info, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR))); + handleConnectionException(server, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR))); break; } }, connection.getChannel().eventLoop()); 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 63890c3f0..bf63dc63d 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 @@ -8,6 +8,7 @@ import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentR import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.VelocityConstants; @@ -220,7 +221,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } private void handleProxyLogin(ConnectedPlayer player) { - Optional toTry = player.getNextServerToTry(); + Optional toTry = player.getNextServerToTry(); if (!toTry.isPresent()) { player.close(TextComponent.of("No available servers", TextColor.RED)); return; @@ -246,9 +247,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { logger.info("{} has connected", player); inbound.setSessionHandler(new InitialConnectSessionHandler(player)); - server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> { - player.createConnectionRequest(toTry.get()).fireAndForget(); - }); + server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget()); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java b/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java new file mode 100644 index 000000000..30835df3e --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java @@ -0,0 +1,68 @@ +package com.velocitypowered.proxy.server; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.proxy.VelocityServer; + +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class ServerMap { + private final VelocityServer server; + private final Map servers = new HashMap<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + public ServerMap(VelocityServer server) { + this.server = server; + } + + public Optional getServer(String name) { + Preconditions.checkNotNull(name, "server"); + String lowerName = name.toLowerCase(Locale.US); + lock.readLock().lock(); + try { + return Optional.ofNullable(servers.get(lowerName)); + } finally { + lock.readLock().unlock(); + } + } + + public Collection getAllServers() { + lock.readLock().lock(); + try { + return ImmutableList.copyOf(servers.values()); + } finally { + lock.readLock().unlock(); + } + } + + public RegisteredServer register(ServerInfo serverInfo) { + Preconditions.checkNotNull(serverInfo, "serverInfo"); + String lowerName = serverInfo.getName().toLowerCase(Locale.US); + lock.writeLock().lock(); + try { + VelocityRegisteredServer rs = new VelocityRegisteredServer(server, serverInfo); + Preconditions.checkArgument(servers.putIfAbsent(lowerName, rs) == null, "Server with name %s already registered", serverInfo.getName()); + return rs; + } finally { + lock.writeLock().unlock(); + } + } + + public void unregister(ServerInfo serverInfo) { + Preconditions.checkNotNull(serverInfo, "serverInfo"); + String lowerName = serverInfo.getName().toLowerCase(Locale.US); + lock.writeLock().lock(); + try { + RegisteredServer rs = servers.get(lowerName); + Preconditions.checkArgument(rs != null, "Server with name %s is not registered!", serverInfo.getName()); + Preconditions.checkArgument(rs.getServerInfo().equals(serverInfo), "Trying to remove server %s with differing information", serverInfo.getName()); + servers.remove(lowerName); + } finally { + lock.writeLock().unlock(); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java new file mode 100644 index 000000000..21c654957 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -0,0 +1,95 @@ +package com.velocitypowered.proxy.server; + +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class VelocityRegisteredServer implements RegisteredServer { + private final VelocityServer server; + private final ServerInfo serverInfo; + private final Set players = new HashSet<>(); + private final ReadWriteLock playersLock = new ReentrantReadWriteLock(); + + public VelocityRegisteredServer(VelocityServer server, ServerInfo serverInfo) { + this.server = server; + this.serverInfo = serverInfo; + } + + @Override + public ServerInfo getServerInfo() { + return serverInfo; + } + + @Override + public Collection getPlayersConnected() { + playersLock.readLock().lock(); + try { + return ImmutableList.copyOf(players); + } finally { + playersLock.readLock().unlock(); + } + } + + @Override + public CompletableFuture ping() { + CompletableFuture p = new CompletableFuture<>(); + p.completeExceptionally(new UnsupportedOperationException("Not currently implemented.")); + return p; + } + + public void addPlayer(ConnectedPlayer player) { + playersLock.writeLock().lock(); + try { + players.add(player); + } finally { + playersLock.writeLock().unlock(); + } + } + + public void removePlayer(ConnectedPlayer player) { + playersLock.writeLock().lock(); + try { + players.remove(player); + } finally { + playersLock.writeLock().unlock(); + } + } + + @Override + public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + ServerConnection backendConnection = null; + playersLock.readLock().lock(); + try { + for (ConnectedPlayer player : players) { + if (player.getConnectedServer() != null && player.getConnectedServer().getServerInfo().equals(serverInfo)) { + backendConnection = player.getConnectedServer(); + break; + } + } + + if (backendConnection == null) { + return false; + } + } finally { + playersLock.readLock().unlock(); + } + + return backendConnection.sendPluginMessage(identifier, data); + } + + @Override + public String toString() { + return "registered server: " + serverInfo; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java b/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java deleted file mode 100644 index 75784ef0b..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/ServerMap.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.velocitypowered.proxy.util; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.velocitypowered.api.proxy.server.ServerInfo; - -import java.util.*; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -public class ServerMap { - private final Map servers = new HashMap<>(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - - public Optional getServer(String server) { - Preconditions.checkNotNull(server, "server"); - String lowerName = server.toLowerCase(Locale.US); - lock.readLock().lock(); - try { - return Optional.ofNullable(servers.get(lowerName)); - } finally { - lock.readLock().unlock(); - } - } - - public Collection getAllServers() { - lock.readLock().lock(); - try { - return ImmutableList.copyOf(servers.values()); - } finally { - lock.readLock().unlock(); - } - } - - public void register(ServerInfo server) { - Preconditions.checkNotNull(server, "server"); - String lowerName = server.getName().toLowerCase(Locale.US); - lock.writeLock().lock(); - try { - Preconditions.checkArgument(servers.putIfAbsent(lowerName, server) == null, "Server with name %s already registered", server.getName()); - } finally { - lock.writeLock().unlock(); - } - } - - public void unregister(ServerInfo server) { - Preconditions.checkNotNull(server, "server"); - String lowerName = server.getName().toLowerCase(Locale.US); - lock.writeLock().lock(); - try { - Preconditions.checkArgument(servers.remove(lowerName, server), "Server with this name is not registered!"); - } finally { - lock.writeLock().unlock(); - } - } -} diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java index e332ed85f..ad8d3d52b 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java @@ -1,6 +1,8 @@ package com.velocitypowered.proxy.util; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.proxy.server.ServerMap; import org.junit.jupiter.api.Test; import java.net.InetAddress; @@ -14,18 +16,18 @@ class ServerMapTest { @Test void respectsCaseInsensitivity() { - ServerMap map = new ServerMap(); + ServerMap map = new ServerMap(null); ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); - map.register(info); + RegisteredServer connection = map.register(info); - assertEquals(Optional.of(info), map.getServer("TestServer")); - assertEquals(Optional.of(info), map.getServer("testserver")); - assertEquals(Optional.of(info), map.getServer("TESTSERVER")); + assertEquals(Optional.of(connection), map.getServer("TestServer")); + assertEquals(Optional.of(connection), map.getServer("testserver")); + assertEquals(Optional.of(connection), map.getServer("TESTSERVER")); } @Test void rejectsRepeatedRegisterAttempts() { - ServerMap map = new ServerMap(); + ServerMap map = new ServerMap(null); ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); map.register(info);