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;
}
/**
* 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() {
this.mods.clear();
return this;

Datei anzeigen

@ -66,6 +66,8 @@ dependencies {
compile 'com.mojang:brigadier:1.0.15'
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-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.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public class VelocityServer implements ProxyServer {
@ -263,11 +264,7 @@ public class VelocityServer implements ProxyServer {
logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
}
public Bootstrap createBootstrap() {
return this.cm.createWorker();
}
public Bootstrap createBootstrap(EventLoopGroup group) {
public Bootstrap createBootstrap(@Nullable EventLoopGroup 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")
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",
"suggest turning this on."})
@Comment({
"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")
private boolean announceForge = false;
@ -82,6 +87,20 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
@ConfigKey("kick-existing-players")
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]")
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,
boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
boolean onlineModeKickExistingPlayers, Servers servers, ForcedHosts forcedHosts,
Advanced advanced, Query query, Metrics metrics) {
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, Servers servers,
ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics) {
this.bind = bind;
this.motd = motd;
this.showMaxPlayers = showMaxPlayers;
@ -124,6 +143,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
this.playerInfoForwardingMode = playerInfoForwardingMode;
this.forwardingSecret = forwardingSecret;
this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers;
this.pingPassthrough = pingPassthrough;
this.servers = servers;
this.forcedHosts = forcedHosts;
this.advanced = advanced;
@ -380,6 +400,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
return metrics;
}
public PingPassthroughMode getPingPassthrough() {
return pingPassthrough;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@ -427,6 +451,8 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN")
.toUpperCase(Locale.US);
String passThroughName = toml.getString("ping-passthrough", "DISABLED")
.toUpperCase(Locale.US);
return new VelocityConfiguration(
toml.getString("bind", "0.0.0.0:25577"),
@ -437,6 +463,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
PlayerInfoForwarding.valueOf(forwardingModeName),
forwardingSecret,
toml.getBoolean("kick-existing-players", false),
PingPassthroughMode.valueOf(passThroughName),
servers,
forcedHosts,
advanced,

Datei anzeigen

@ -1,12 +1,15 @@
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;
@ -15,7 +18,13 @@ 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 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 {
@ -30,12 +39,10 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
this.inboundWrapper = inboundWrapper;
}
private ServerPing createInitialPing() {
private ServerPing constructLocalPing(ProtocolVersion version) {
VelocityConfiguration configuration = server.getConfiguration();
ProtocolVersion shownVersion = ProtocolVersion.isSupported(connection.getProtocolVersion())
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
return new ServerPing(
new ServerPing.Version(shownVersion.getProtocol(),
new ServerPing.Version(version.getProtocol(),
"Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING),
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
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
public boolean handle(LegacyPing packet) {
ServerPing initialPing = createInitialPing();
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
server.getEventManager().fire(event)
.thenRunAsync(() -> {
createInitialPing()
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inboundWrapper,
ping)))
.thenAcceptAsync(event -> {
connection.closeWith(LegacyDisconnect.fromServerPing(event.getPing(),
packet.getVersion()));
}, connection.eventLoop());
@ -65,11 +129,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(StatusRequest packet) {
ServerPing initialPing = createInitialPing();
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
server.getEventManager().fire(event)
.thenRunAsync(
() -> {
createInitialPing()
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inboundWrapper,
ping)))
.thenAcceptAsync(
(event) -> {
StringBuilder json = new StringBuilder();
VelocityServer.GSON.toJson(event.getPing(), 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.
*

Datei anzeigen

@ -18,13 +18,15 @@ public class PingSessionHandler implements MinecraftSessionHandler {
private final CompletableFuture<ServerPing> result;
private final RegisteredServer server;
private final MinecraftConnection connection;
private final ProtocolVersion version;
private boolean completed = false;
PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server,
MinecraftConnection connection) {
MinecraftConnection connection, ProtocolVersion version) {
this.result = result;
this.server = server;
this.connection = connection;
this.version = version;
}
@Override
@ -33,7 +35,7 @@ public class PingSessionHandler implements MinecraftSessionHandler {
handshake.setNextStatus(StateRegistry.STATUS_ID);
handshake.setServerAddress(server.getServerInfo().getAddress().getHostString());
handshake.setPort(server.getServerInfo().getAddress().getPort());
handshake.setProtocolVersion(ProtocolVersion.MINIMUM_VERSION);
handshake.setProtocolVersion(version);
connection.delayedWrite(handshake);
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.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
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.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoop;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.Collection;
import java.util.Set;
@ -58,11 +60,22 @@ public class VelocityRegisteredServer implements RegisteredServer {
@Override
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) {
throw new IllegalStateException("No Velocity proxy instance available");
}
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.createBootstrap()
server.createBootstrap(loop)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
@ -86,8 +99,8 @@ public class VelocityRegisteredServer implements RegisteredServer {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
conn.setSessionHandler(
new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn));
conn.setSessionHandler(new PingSessionHandler(
pingFuture, VelocityRegisteredServer.this, conn, version));
} else {
pingFuture.completeExceptionally(future.cause());
}