3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-06 00:00:47 +01:00

Add support for ping pass-through

By default, ping pass-through is not enabled. However, you can use
ping passthrough to pass through just mods (great for modded servers)
or everything.
Dieser Commit ist enthalten in:
Andrew Steinborn 2019-08-06 02:06:53 -04:00
Ursprung 9f3d1a2390
Commit ca9a4492c4
9 geänderte Dateien mit 152 neuen und 31 gelöschten Zeilen

Datei anzeigen

@ -182,6 +182,19 @@ public final class ServerPing {
return this; return this;
} }
/**
* Uses the modified {@code mods} list in the response.
* @param mods the mods list to use
* @return this build, for chaining
*/
public Builder mods(ModInfo mods) {
Preconditions.checkNotNull(mods, "mods");
this.modType = mods.getType();
this.mods.clear();
this.mods.addAll(mods.getMods());
return this;
}
public Builder clearMods() { public Builder clearMods() {
this.mods.clear(); this.mods.clear();
return this; return this;

Datei anzeigen

@ -67,6 +67,8 @@ dependencies {
compile 'org.asynchttpclient:async-http-client:2.10.1' compile 'org.asynchttpclient:async-http-client:2.10.1'
compile 'com.spotify:completable-futures:0.3.2'
testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
} }

Datei anzeigen

@ -77,6 +77,7 @@ import org.asynchttpclient.AsyncHttpClient;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public class VelocityServer implements ProxyServer { public class VelocityServer implements ProxyServer {
@ -263,11 +264,7 @@ public class VelocityServer implements ProxyServer {
logger.info("Loaded {} plugins", pluginManager.getPlugins().size()); logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
} }
public Bootstrap createBootstrap() { public Bootstrap createBootstrap(@Nullable EventLoopGroup group) {
return this.cm.createWorker();
}
public Bootstrap createBootstrap(EventLoopGroup group) {
return this.cm.createWorker(group); return this.cm.createWorker(group);
} }

Datei anzeigen

@ -0,0 +1,7 @@
package com.velocitypowered.proxy.config;
public enum PingPassthroughMode {
DISABLED,
MODS,
ALL
}

Datei anzeigen

@ -72,8 +72,13 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
@ConfigKey("forwarding-secret") @ConfigKey("forwarding-secret")
private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8);
@Comment({"Announce whether or not your server supports Forge. If you run a modded server, we", @Comment({
"suggest turning this on."}) "Announce whether or not your server supports Forge. If you run a modded server, we",
"suggest turning this on.",
"",
"If your network runs one modpack consistently, consider using ping-passthrough = \"mods\"",
"instead for a nicer display in the server list."
})
@ConfigKey("announce-forge") @ConfigKey("announce-forge")
private boolean announceForge = false; private boolean announceForge = false;
@ -82,6 +87,20 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
@ConfigKey("kick-existing-players") @ConfigKey("kick-existing-players")
private boolean onlineModeKickExistingPlayers = false; private boolean onlineModeKickExistingPlayers = false;
@Comment({
"Should Velocity pass server list ping requests to a backend server?",
"Available options:",
"- \"disabled\": No pass-through will be done. The velocity.toml and server-icon.png",
" will determine the initial server list ping response.",
"- \"mods\": Passes only the mod list from your backend server into the response.",
" This is the recommended replacement for announce-forge = true. If no backend",
" servers can be contacted, Velocity will not display any mod information.",
"- \"all\": Passes everything from the backend server into the response. The Velocity",
" configuration is used if no servers could be contacted."
})
@ConfigKey("ping-passthrough")
private PingPassthroughMode pingPassthrough;
@Table("[servers]") @Table("[servers]")
private final Servers servers; private final Servers servers;
@ -114,8 +133,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
boolean onlineModeKickExistingPlayers, Servers servers, ForcedHosts forcedHosts, boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, Servers servers,
Advanced advanced, Query query, Metrics metrics) { ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics) {
this.bind = bind; this.bind = bind;
this.motd = motd; this.motd = motd;
this.showMaxPlayers = showMaxPlayers; this.showMaxPlayers = showMaxPlayers;
@ -124,6 +143,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
this.playerInfoForwardingMode = playerInfoForwardingMode; this.playerInfoForwardingMode = playerInfoForwardingMode;
this.forwardingSecret = forwardingSecret; this.forwardingSecret = forwardingSecret;
this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers; this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers;
this.pingPassthrough = pingPassthrough;
this.servers = servers; this.servers = servers;
this.forcedHosts = forcedHosts; this.forcedHosts = forcedHosts;
this.advanced = advanced; this.advanced = advanced;
@ -380,6 +400,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return metrics; return metrics;
} }
public PingPassthroughMode getPingPassthrough() {
return pingPassthrough;
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@ -427,6 +451,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN") String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN")
.toUpperCase(Locale.US); .toUpperCase(Locale.US);
String passThroughName = toml.getString("ping-passthrough", "DISABLED")
.toUpperCase(Locale.US);
return new VelocityConfiguration( return new VelocityConfiguration(
toml.getString("bind", "0.0.0.0:25577"), toml.getString("bind", "0.0.0.0:25577"),
@ -437,6 +463,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
PlayerInfoForwarding.valueOf(forwardingModeName), PlayerInfoForwarding.valueOf(forwardingModeName),
forwardingSecret, forwardingSecret,
toml.getBoolean("kick-existing-players", false), toml.getBoolean("kick-existing-players", false),
PingPassthroughMode.valueOf(passThroughName),
servers, servers,
forcedHosts, forcedHosts,
advanced, advanced,

Datei anzeigen

@ -1,12 +1,15 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.spotify.futures.CompletableFutures;
import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.api.util.ModInfo;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PingPassthroughMode;
import com.velocitypowered.proxy.config.VelocityConfiguration; 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;
@ -15,7 +18,13 @@ import com.velocitypowered.proxy.protocol.packet.LegacyPing;
import com.velocitypowered.proxy.protocol.packet.StatusPing; import com.velocitypowered.proxy.protocol.packet.StatusPing;
import com.velocitypowered.proxy.protocol.packet.StatusRequest; import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse; import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
public class StatusSessionHandler implements MinecraftSessionHandler { public class StatusSessionHandler implements MinecraftSessionHandler {
@ -30,12 +39,10 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
this.inboundWrapper = inboundWrapper; this.inboundWrapper = inboundWrapper;
} }
private ServerPing createInitialPing() { private ServerPing constructLocalPing(ProtocolVersion version) {
VelocityConfiguration configuration = server.getConfiguration(); VelocityConfiguration configuration = server.getConfiguration();
ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion())
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
return new ServerPing( return new ServerPing(
new ServerPing.Version(shownVersion.getProtocol(), new ServerPing.Version(version.getProtocol(),
"Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING), "Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING),
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
ImmutableList.of()), ImmutableList.of()),
@ -45,12 +52,69 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
); );
} }
private CompletableFuture<ServerPing> createInitialPing() {
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 {
return attemptPingPassthrough(configuration.getPingPassthrough(),
configuration.getAttemptConnectionOrder(), shownVersion);
}
}
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
return responses.stream()
.filter(ping -> ping != fallback)
.findFirst()
.orElse(fallback);
});
case MODS:
return pingResponses.thenApply(responses -> {
// Find the first non-fallback that contains a non-empty mod list
Optional<ModInfo> modInfo = responses.stream()
.filter(ping -> ping != fallback)
.map(ServerPing::getModinfo)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
return modInfo.map(mi -> fallback.asBuilder().mods(mi).build()).orElse(fallback);
});
default:
// Not possible, but covered for completeness.
return CompletableFuture.completedFuture(fallback);
}
}
@Override @Override
public boolean handle(LegacyPing packet) { public boolean handle(LegacyPing packet) {
ServerPing initialPing = createInitialPing(); createInitialPing()
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing); .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inboundWrapper,
server.getEventManager().fire(event) ping)))
.thenRunAsync(() -> { .thenAcceptAsync(event -> {
connection.closeWith(LegacyDisconnect.fromServerPing(event.getPing(), connection.closeWith(LegacyDisconnect.fromServerPing(event.getPing(),
packet.getVersion())); packet.getVersion()));
}, connection.eventLoop()); }, connection.eventLoop());
@ -65,11 +129,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(StatusRequest packet) { public boolean handle(StatusRequest packet) {
ServerPing initialPing = createInitialPing(); createInitialPing()
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing); .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inboundWrapper,
server.getEventManager().fire(event) ping)))
.thenRunAsync( .thenAcceptAsync(
() -> { (event) -> {
StringBuilder json = new StringBuilder(); StringBuilder json = new StringBuilder();
VelocityServer.GSON.toJson(event.getPing(), json); VelocityServer.GSON.toJson(event.getPing(), json);
connection.write(new StatusResponse(json)); connection.write(new StatusResponse(json));

Datei anzeigen

@ -145,10 +145,6 @@ public final class ConnectionManager {
}); });
} }
public Bootstrap createWorker() {
return this.createWorker(null);
}
/** /**
* Creates a TCP {@link Bootstrap} using Velocity's event loops. * Creates a TCP {@link Bootstrap} using Velocity's event loops.
* *

Datei anzeigen

@ -18,13 +18,15 @@ public class PingSessionHandler implements MinecraftSessionHandler {
private final CompletableFuture<ServerPing> result; private final CompletableFuture<ServerPing> result;
private final RegisteredServer server; private final RegisteredServer server;
private final MinecraftConnection connection; private final MinecraftConnection connection;
private final ProtocolVersion version;
private boolean completed = false; private boolean completed = false;
PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server, PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server,
MinecraftConnection connection) { MinecraftConnection connection, ProtocolVersion version) {
this.result = result; this.result = result;
this.server = server; this.server = server;
this.connection = connection; this.connection = connection;
this.version = version;
} }
@Override @Override
@ -33,7 +35,7 @@ public class PingSessionHandler implements MinecraftSessionHandler {
handshake.setNextStatus(StateRegistry.STATUS_ID); handshake.setNextStatus(StateRegistry.STATUS_ID);
handshake.setServerAddress(server.getServerInfo().getAddress().getHostString()); handshake.setServerAddress(server.getServerInfo().getAddress().getHostString());
handshake.setPort(server.getServerInfo().getAddress().getPort()); handshake.setPort(server.getServerInfo().getAddress().getPort());
handshake.setProtocolVersion(ProtocolVersion.MINIMUM_VERSION); handshake.setProtocolVersion(version);
connection.delayedWrite(handshake); connection.delayedWrite(handshake);
connection.setState(StateRegistry.STATUS); connection.setState(StateRegistry.STATUS);

Datei anzeigen

@ -9,6 +9,7 @@ import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
@ -27,6 +28,7 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoop;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
@ -58,11 +60,22 @@ public class VelocityRegisteredServer implements RegisteredServer {
@Override @Override
public CompletableFuture<ServerPing> ping() { public CompletableFuture<ServerPing> ping() {
return ping(null, ProtocolVersion.UNKNOWN);
}
/**
* Pings the specified server using the specified event {@code loop}, claiming to be
* {@code version}.
* @param loop the event loop to use
* @param version the version to report
* @return the server list ping response
*/
public CompletableFuture<ServerPing> ping(@Nullable EventLoop loop, ProtocolVersion version) {
if (server == null) { if (server == null) {
throw new IllegalStateException("No Velocity proxy instance available"); throw new IllegalStateException("No Velocity proxy instance available");
} }
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>(); CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.createBootstrap() server.createBootstrap(loop)
.handler(new ChannelInitializer<Channel>() { .handler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
@ -86,8 +99,8 @@ public class VelocityRegisteredServer implements RegisteredServer {
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) { if (future.isSuccess()) {
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
conn.setSessionHandler( conn.setSessionHandler(new PingSessionHandler(
new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn)); pingFuture, VelocityRegisteredServer.this, conn, version));
} else { } else {
pingFuture.completeExceptionally(future.cause()); pingFuture.completeExceptionally(future.cause());
} }