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:
Ursprung
9f3d1a2390
Commit
ca9a4492c4
@ -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;
|
||||
|
@ -67,6 +67,8 @@ dependencies {
|
||||
|
||||
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}"
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
public enum PingPassthroughMode {
|
||||
DISABLED,
|
||||
MODS,
|
||||
ALL
|
||||
}
|
@ -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,
|
||||
|
@ -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));
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren