mirror of
https://github.com/PaperMC/Velocity.git
synced 2024-11-27 06:30:35 +08:00
Implement the ServerData packet by firing ProxyPingEvent (#771)
* Implement the ServerData packet by firing ProxyPingEvent Mojang introduced the enable-status server property with Minecraft 1.19, which if enabled causes servers to close the connection when a client tries to ping them. Mojang wants to show the MOTD and favicon on the server select screen for those who manage to log in, so we need to implement this packet as well. The good news is that we can send this packet as many times as needed on the same connection This matches the behavior of pinging the server. This is a minor, but completely backwards-compatible, API breakage: Player inherits from InboundConnection so we do not have to change ProxyPingEvent, however plugins not expecting a Player might get confused. * typo
This commit is contained in:
parent
86c65f3910
commit
662fbc4e3c
@ -13,10 +13,11 @@ import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
|
||||
/**
|
||||
* This event is fired when a server list ping request is sent by a remote client. Velocity will
|
||||
* This event is fired when a request for server information is sent by a remote client, or when the
|
||||
* server sends the MOTD and favicon to the client after a successful login. Velocity will
|
||||
* wait on this event to finish firing before delivering the results to the remote client, but
|
||||
* you are urged to be as parsimonious as possible when handling this event due to the amount of
|
||||
* ping packets a client can send.
|
||||
* you are urged to handle this event as quickly as possible when handling this event due to the
|
||||
* amount of ping packets a client can send.
|
||||
*/
|
||||
@AwaitingEvent
|
||||
public final class ProxyPingEvent {
|
||||
|
@ -44,6 +44,7 @@ import com.velocitypowered.proxy.command.builtin.VelocityCommand;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
||||
import com.velocitypowered.proxy.connection.util.ServerListPingHandler;
|
||||
import com.velocitypowered.proxy.console.VelocityConsole;
|
||||
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
||||
import com.velocitypowered.proxy.event.VelocityEventManager;
|
||||
@ -142,6 +143,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
private final VelocityEventManager eventManager;
|
||||
private final VelocityScheduler scheduler;
|
||||
private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar();
|
||||
private ServerListPingHandler serverListPingHandler;
|
||||
|
||||
VelocityServer(final ProxyOptions options) {
|
||||
pluginManager = new VelocityPluginManager(this);
|
||||
@ -151,6 +153,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
console = new VelocityConsole(this);
|
||||
cm = new ConnectionManager(this);
|
||||
servers = new ServerMap(this);
|
||||
serverListPingHandler = new ServerListPingHandler(this);
|
||||
this.options = options;
|
||||
this.bossBarManager = new AdventureBossBarManager();
|
||||
}
|
||||
@ -370,6 +373,10 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
return this.cm.backendChannelInitializer.get();
|
||||
}
|
||||
|
||||
public ServerListPingHandler getServerListPingHandler() {
|
||||
return serverListPingHandler;
|
||||
}
|
||||
|
||||
public boolean isShutdown() {
|
||||
return shutdown;
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerData;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
@ -261,4 +262,8 @@ public interface MinecraftSessionHandler {
|
||||
default boolean handle(PlayerCommand packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(ServerData serverData) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
@ -46,6 +47,7 @@ import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerData;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@ -268,6 +270,22 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(ServerData packet) {
|
||||
server.getServerListPingHandler().getInitialPing(this.serverConn.getPlayer())
|
||||
.thenComposeAsync(
|
||||
ping -> server.getEventManager().fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)),
|
||||
playerConnection.eventLoop()
|
||||
)
|
||||
.thenAcceptAsync(pingEvent ->
|
||||
this.playerConnection.write(
|
||||
new ServerData(pingEvent.getPing().getDescriptionComponent(),
|
||||
pingEvent.getPing().getFavicon().orElse(null),
|
||||
packet.isPreviewsChat())
|
||||
), playerConnection.eventLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
if (packet instanceof PluginMessage) {
|
||||
|
@ -56,6 +56,7 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
@ -112,7 +113,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable {
|
||||
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable,
|
||||
VelocityInboundConnection {
|
||||
|
||||
private static final int MAX_PLUGIN_CHANNELS = 1024;
|
||||
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = PlainTextComponentSerializer.builder()
|
||||
|
@ -29,6 +29,7 @@ import com.velocitypowered.proxy.connection.ConnectionTypes;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants;
|
||||
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
@ -60,7 +61,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public boolean handle(LegacyPing packet) {
|
||||
connection.setProtocolVersion(ProtocolVersion.LEGACY);
|
||||
StatusSessionHandler handler = new StatusSessionHandler(server, connection,
|
||||
StatusSessionHandler handler = new StatusSessionHandler(server,
|
||||
new LegacyInboundConnection(connection, packet));
|
||||
connection.setSessionHandler(handler);
|
||||
handler.handle(packet);
|
||||
@ -91,7 +92,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
switch (nextState) {
|
||||
case STATUS:
|
||||
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
|
||||
connection.setSessionHandler(new StatusSessionHandler(server, ic));
|
||||
break;
|
||||
case LOGIN:
|
||||
this.handleLogin(handshake, ic);
|
||||
@ -197,7 +198,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
connection.close(true);
|
||||
}
|
||||
|
||||
private static class LegacyInboundConnection implements InboundConnection {
|
||||
private static class LegacyInboundConnection implements VelocityInboundConnection {
|
||||
|
||||
private final MinecraftConnection connection;
|
||||
private final LegacyPing ping;
|
||||
@ -232,5 +233,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
public String toString() {
|
||||
return "[legacy connection] " + this.getRemoteAddress().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MinecraftConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
|
||||
@ -33,7 +34,7 @@ import net.kyori.adventure.translation.GlobalTranslator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public final class InitialInboundConnection implements InboundConnection,
|
||||
public final class InitialInboundConnection implements VelocityInboundConnection,
|
||||
MinecraftConnectionAssociation {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(InitialInboundConnection.class);
|
||||
@ -74,6 +75,7 @@ public final class InitialInboundConnection implements InboundConnection,
|
||||
return "[initial connection] " + connection.getRemoteAddress().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MinecraftConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
@ -17,33 +17,18 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.spotify.futures.CompletableFutures;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.api.util.ModInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PingPassthroughMode;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyDisconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyPing;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusPing;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@ -55,13 +40,12 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private final VelocityServer server;
|
||||
private final MinecraftConnection connection;
|
||||
private final InboundConnection inbound;
|
||||
private final VelocityInboundConnection inbound;
|
||||
private boolean pingReceived = false;
|
||||
|
||||
StatusSessionHandler(VelocityServer server, MinecraftConnection connection,
|
||||
InboundConnection inbound) {
|
||||
StatusSessionHandler(VelocityServer server, VelocityInboundConnection inbound) {
|
||||
this.server = server;
|
||||
this.connection = connection;
|
||||
this.connection = inbound.getConnection();
|
||||
this.inbound = inbound;
|
||||
}
|
||||
|
||||
@ -73,116 +57,13 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private ServerPing constructLocalPing(ProtocolVersion version) {
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
return new ServerPing(
|
||||
new ServerPing.Version(version.getProtocol(),
|
||||
"Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING),
|
||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
|
||||
ImmutableList.of()),
|
||||
configuration.getMotd(),
|
||||
configuration.getFavicon().orElse(null),
|
||||
configuration.isAnnounceForge() ? ModInfo.DEFAULT : null
|
||||
);
|
||||
}
|
||||
|
||||
private CompletableFuture<ServerPing> attemptPingPassthrough(PingPassthroughMode mode,
|
||||
List<String> servers, ProtocolVersion pingingVersion) {
|
||||
ServerPing fallback = constructLocalPing(pingingVersion);
|
||||
List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
|
||||
for (String s : servers) {
|
||||
Optional<RegisteredServer> rs = server.getServer(s);
|
||||
if (!rs.isPresent()) {
|
||||
continue;
|
||||
}
|
||||
VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get();
|
||||
pings.add(vrs.ping(connection.eventLoop(), pingingVersion));
|
||||
}
|
||||
if (pings.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(fallback);
|
||||
}
|
||||
|
||||
CompletableFuture<List<ServerPing>> pingResponses = CompletableFutures.successfulAsList(pings,
|
||||
(ex) -> fallback);
|
||||
switch (mode) {
|
||||
case ALL:
|
||||
return pingResponses.thenApply(responses -> {
|
||||
// Find the first non-fallback
|
||||
for (ServerPing response : responses) {
|
||||
if (response == fallback) {
|
||||
continue;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
return fallback;
|
||||
});
|
||||
case MODS:
|
||||
return pingResponses.thenApply(responses -> {
|
||||
// Find the first non-fallback that contains a mod list
|
||||
for (ServerPing response : responses) {
|
||||
if (response == fallback) {
|
||||
continue;
|
||||
}
|
||||
Optional<ModInfo> modInfo = response.getModinfo();
|
||||
if (modInfo.isPresent()) {
|
||||
return fallback.asBuilder().mods(modInfo.get()).build();
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
});
|
||||
case DESCRIPTION:
|
||||
return pingResponses.thenApply(responses -> {
|
||||
// Find the first non-fallback. If it includes a modlist, add it too.
|
||||
for (ServerPing response : responses) {
|
||||
if (response == fallback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (response.getDescriptionComponent() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new ServerPing(
|
||||
fallback.getVersion(),
|
||||
fallback.getPlayers().orElse(null),
|
||||
response.getDescriptionComponent(),
|
||||
fallback.getFavicon().orElse(null),
|
||||
response.getModinfo().orElse(null)
|
||||
);
|
||||
}
|
||||
return fallback;
|
||||
});
|
||||
default:
|
||||
// Not possible, but covered for completeness.
|
||||
return CompletableFuture.completedFuture(fallback);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<ServerPing> getInitialPing() {
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion())
|
||||
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
|
||||
PingPassthroughMode passthrough = configuration.getPingPassthrough();
|
||||
|
||||
if (passthrough == PingPassthroughMode.DISABLED) {
|
||||
return CompletableFuture.completedFuture(constructLocalPing(shownVersion));
|
||||
} else {
|
||||
String virtualHostStr = inbound.getVirtualHost().map(InetSocketAddress::getHostString)
|
||||
.map(str -> str.toLowerCase(Locale.ROOT))
|
||||
.orElse("");
|
||||
List<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(
|
||||
virtualHostStr, server.getConfiguration().getAttemptConnectionOrder());
|
||||
return attemptPingPassthrough(configuration.getPingPassthrough(), serversToTry, shownVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LegacyPing packet) {
|
||||
if (this.pingReceived) {
|
||||
throw EXPECTED_AWAITING_REQUEST;
|
||||
}
|
||||
this.pingReceived = true;
|
||||
getInitialPing()
|
||||
server.getServerListPingHandler().getInitialPing(this.inbound)
|
||||
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
||||
.thenAcceptAsync(event -> connection.closeWith(
|
||||
LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())),
|
||||
@ -207,7 +88,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
this.pingReceived = true;
|
||||
|
||||
getInitialPing()
|
||||
this.server.getServerListPingHandler().getInitialPing(inbound)
|
||||
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
||||
.thenAcceptAsync(
|
||||
(event) -> {
|
||||
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.spotify.futures.CompletableFutures;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.api.util.ModInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PingPassthroughMode;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ServerListPingHandler {
|
||||
|
||||
private final VelocityServer server;
|
||||
|
||||
public ServerListPingHandler(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
private ServerPing constructLocalPing(ProtocolVersion version) {
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
return new ServerPing(
|
||||
new ServerPing.Version(version.getProtocol(),
|
||||
"Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING),
|
||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
|
||||
ImmutableList.of()),
|
||||
configuration.getMotd(),
|
||||
configuration.getFavicon().orElse(null),
|
||||
configuration.isAnnounceForge() ? ModInfo.DEFAULT : null
|
||||
);
|
||||
}
|
||||
|
||||
private CompletableFuture<ServerPing> attemptPingPassthrough(VelocityInboundConnection connection,
|
||||
PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion) {
|
||||
ServerPing fallback = constructLocalPing(connection.getProtocolVersion());
|
||||
List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
|
||||
for (String s : servers) {
|
||||
Optional<RegisteredServer> rs = server.getServer(s);
|
||||
if (!rs.isPresent()) {
|
||||
continue;
|
||||
}
|
||||
VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get();
|
||||
pings.add(vrs.ping(connection.getConnection().eventLoop(), responseProtocolVersion));
|
||||
}
|
||||
if (pings.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(fallback);
|
||||
}
|
||||
|
||||
CompletableFuture<List<ServerPing>> pingResponses = CompletableFutures.successfulAsList(pings,
|
||||
(ex) -> fallback);
|
||||
switch (mode) {
|
||||
case ALL:
|
||||
return pingResponses.thenApply(responses -> {
|
||||
// Find the first non-fallback
|
||||
for (ServerPing response : responses) {
|
||||
if (response == fallback) {
|
||||
continue;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
return fallback;
|
||||
});
|
||||
case MODS:
|
||||
return pingResponses.thenApply(responses -> {
|
||||
// Find the first non-fallback that contains a mod list
|
||||
for (ServerPing response : responses) {
|
||||
if (response == fallback) {
|
||||
continue;
|
||||
}
|
||||
Optional<ModInfo> modInfo = response.getModinfo();
|
||||
if (modInfo.isPresent()) {
|
||||
return fallback.asBuilder().mods(modInfo.get()).build();
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
});
|
||||
case DESCRIPTION:
|
||||
return pingResponses.thenApply(responses -> {
|
||||
// Find the first non-fallback. If it includes a modlist, add it too.
|
||||
for (ServerPing response : responses) {
|
||||
if (response == fallback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (response.getDescriptionComponent() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new ServerPing(
|
||||
fallback.getVersion(),
|
||||
fallback.getPlayers().orElse(null),
|
||||
response.getDescriptionComponent(),
|
||||
fallback.getFavicon().orElse(null),
|
||||
response.getModinfo().orElse(null)
|
||||
);
|
||||
}
|
||||
return fallback;
|
||||
});
|
||||
// Not possible, but covered for completeness.
|
||||
default:
|
||||
return CompletableFuture.completedFuture(fallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the "default" server ping for a player.
|
||||
*
|
||||
* @param connection the connection
|
||||
* @return a future with the initial ping result
|
||||
*/
|
||||
public CompletableFuture<ServerPing> getInitialPing(VelocityInboundConnection connection) {
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion())
|
||||
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
|
||||
PingPassthroughMode passthroughMode = configuration.getPingPassthrough();
|
||||
|
||||
if (passthroughMode == PingPassthroughMode.DISABLED) {
|
||||
return CompletableFuture.completedFuture(constructLocalPing(shownVersion));
|
||||
} else {
|
||||
String virtualHostStr = connection.getVirtualHost().map(InetSocketAddress::getHostString)
|
||||
.map(str -> str.toLowerCase(Locale.ROOT))
|
||||
.orElse("");
|
||||
List<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(
|
||||
virtualHostStr, server.getConfiguration().getAttemptConnectionOrder());
|
||||
return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
|
||||
public interface VelocityInboundConnection extends InboundConnection {
|
||||
MinecraftConnection getConnection();
|
||||
}
|
@ -56,6 +56,7 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerData;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
@ -310,6 +311,8 @@ public enum StateRegistry {
|
||||
map(0x34, MINECRAFT_1_19, false));
|
||||
clientbound.register(SystemChat.class, SystemChat::new,
|
||||
map(0x5F, MINECRAFT_1_19, true));
|
||||
clientbound.register(ServerData.class, ServerData::new,
|
||||
map(0x3F, MINECRAFT_1_19, true));
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.protocol.packet;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ServerData implements MinecraftPacket {
|
||||
|
||||
private @Nullable Component description;
|
||||
private @Nullable Favicon favicon;
|
||||
private boolean previewsChat;
|
||||
|
||||
public ServerData() {
|
||||
}
|
||||
|
||||
public ServerData(@Nullable Component description, @Nullable Favicon favicon,
|
||||
boolean previewsChat) {
|
||||
this.description = description;
|
||||
this.favicon = favicon;
|
||||
this.previewsChat = previewsChat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
|
||||
ProtocolVersion protocolVersion) {
|
||||
if (buf.readBoolean()) {
|
||||
this.description = ProtocolUtils.getJsonChatSerializer(protocolVersion)
|
||||
.deserialize(ProtocolUtils.readString(buf));
|
||||
}
|
||||
if (buf.readBoolean()) {
|
||||
this.favicon = new Favicon(ProtocolUtils.readString(buf));
|
||||
}
|
||||
this.previewsChat = buf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
boolean hasDescription = this.description != null;
|
||||
buf.writeBoolean(hasDescription);
|
||||
if (hasDescription) {
|
||||
ProtocolUtils.writeString(
|
||||
buf,
|
||||
ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(this.description)
|
||||
);
|
||||
}
|
||||
|
||||
boolean hasFavicon = this.favicon != null;
|
||||
buf.writeBoolean(hasFavicon);
|
||||
if (hasFavicon) {
|
||||
ProtocolUtils.writeString(buf, favicon.getBase64Url());
|
||||
}
|
||||
|
||||
buf.writeBoolean(this.previewsChat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
public Component getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Favicon getFavicon() {
|
||||
return favicon;
|
||||
}
|
||||
|
||||
public boolean isPreviewsChat() {
|
||||
return previewsChat;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user