diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java b/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java index 718e57f1..aa73cefd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java @@ -16,13 +16,49 @@ */ package com.velocitypowered.proxy.config; +import com.electronwill.nightconfig.core.Config; /** - * Supported passthrough modes for ping passthrough. + * Object to contain all the things that can be toggled for ping passthrough. + * + * @param version Whether the version should be passed through. + * @param players Whether the player count should be passed through. + * @param description Whether the description should be passed through. + * @param favicon Whether the favicon should be passed through. + * @param modinfo Whether the modinfo should be passed through. */ -public enum PingPassthroughMode { - DISABLED, - MODS, - DESCRIPTION, - ALL +public record PingPassthroughMode(boolean version, boolean players, + boolean description, boolean favicon, boolean modinfo) { + public static final PingPassthroughMode DEFAULT = new PingPassthroughMode(); + + /** + * Creates a default PingPassthroughMode. + */ + private PingPassthroughMode() { + this(false, false, false, false, false); + } + + /** + * Returns a PingPassthroughMode from a config section, or the default if the section is null. + * Based on the code for PacketLimiterConfig. + * + * @param config The configuration object to parse. + * @return The PingPassthroughMode, or the default if {@code config} is null. + */ + public static PingPassthroughMode fromConfig(Config config) { + if (config == null) { + return DEFAULT; + } + return new PingPassthroughMode( + config.getOrElse("version", DEFAULT.version()), + config.getOrElse("players", DEFAULT.players()), + config.getOrElse("description", DEFAULT.description()), + config.getOrElse("favicon", DEFAULT.favicon()), + config.getOrElse("modinfo", DEFAULT.modinfo())); + } + + public boolean enabled() { + return this.version || this.players || this.description || this.favicon + || this.modinfo; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 2c7826e0..fc9ba583 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -32,6 +32,7 @@ import com.velocitypowered.proxy.config.migration.KeyAuthenticationMigration; import com.velocitypowered.proxy.config.migration.MiniMessageTranslationsMigration; import com.velocitypowered.proxy.config.migration.MotdMigration; import com.velocitypowered.proxy.config.migration.PacketLimiterMigration; +import com.velocitypowered.proxy.config.migration.PingPassthroughMigration; import com.velocitypowered.proxy.config.migration.TransferIntegrationMigration; import com.velocitypowered.proxy.util.AddressUtil; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -79,7 +80,7 @@ public class VelocityConfiguration implements ProxyConfig { @Expose private boolean onlineModeKickExistingPlayers = false; @Expose - private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; + private PingPassthroughMode pingPassthrough = PingPassthroughMode.DEFAULT; @Expose private boolean samplePlayersInPing = false; private final Servers servers; @@ -513,7 +514,8 @@ public class VelocityConfiguration implements ProxyConfig { new MotdMigration(), new MiniMessageTranslationsMigration(), new TransferIntegrationMigration(), - new PacketLimiterMigration() + new PacketLimiterMigration(), + new PingPassthroughMigration(), }; for (final ConfigurationMigration migration : migrations) { @@ -555,9 +557,7 @@ public class VelocityConfiguration implements ProxyConfig { final CommentedConfig metricsConfig = config.get("metrics"); final PlayerInfoForwarding forwardingMode = config.getEnumOrElse( "player-info-forwarding-mode", PlayerInfoForwarding.NONE); - final PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough", - PingPassthroughMode.DISABLED); - + final PingPassthroughMode pingPassthrough = PingPassthroughMode.fromConfig(config.get("ping-passthrough")); final boolean samplePlayersInPing = config.getOrElse("sample-players-in-ping", false); final String bind = config.getOrElse("bind", "0.0.0.0:25565"); @@ -590,7 +590,7 @@ public class VelocityConfiguration implements ProxyConfig { forwardingMode, forwardingSecret, kickExisting, - pingPassthroughMode, + pingPassthrough, samplePlayersInPing, enablePlayerAddressLogging, new Servers(serversConfig), diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java index 7c00b7bb..27891369 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java @@ -30,7 +30,8 @@ public sealed interface ConfigurationMigration MotdMigration, MiniMessageTranslationsMigration, TransferIntegrationMigration, - PacketLimiterMigration { + PacketLimiterMigration, + PingPassthroughMigration { boolean shouldMigrate(CommentedFileConfig config); void migrate(CommentedFileConfig config, Logger logger) throws IOException; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PingPassthroughMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PingPassthroughMigration.java new file mode 100644 index 00000000..702a06cf --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PingPassthroughMigration.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024-2026 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 . + */ + +package com.velocitypowered.proxy.config.migration; + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import java.util.Locale; +import org.apache.logging.log4j.Logger; + +/** + * Migrate the old ping passthrough entry to separate config entries. + */ +public final class PingPassthroughMigration implements ConfigurationMigration { + @Override + public boolean shouldMigrate(final CommentedFileConfig config) { + return configVersion(config) < 2.9; + } + + @Override + public void migrate(final CommentedFileConfig config, final Logger logger) { + // Get legacy ping passthrough value + final String legacyMode = config.getOrElse("ping-passthrough", "DISABLED") + .toUpperCase(Locale.ROOT); + boolean version = false; + boolean players = false; + boolean description = false; + boolean favicon = false; + boolean modinfo = false; + + switch (legacyMode) { + case "ALL": + version = true; + players = true; + description = true; + favicon = true; + modinfo = true; + break; + case "DESCRIPTION": + description = true; + modinfo = true; + break; + case "MODS": + modinfo = true; + break; + default: // DISABLED + break; + } + + config.removeComment("ping-passthrough"); + config.remove("ping-passthrough"); + + config.setComment("announce-forge", + " Announce whether or not your server supports Forge. If you run a modded server, we\n" + + " suggest turning this on.\n" + + "\n" + + " If your network runs one modpack consistently, consider using ping-passthrough.modinfo = true\n" + + " instead for a nicer display in the server list."); + + config.set("ping-passthrough.version", version); + config.setComment( + "ping-passthrough.version", + " Should Velocity pass the version number from the backend server when responding to server list ping requests?" + ); + + config.set("ping-passthrough.players", players); + config.setComment( + "ping-passthrough.players", + " Should Velocity pass the player count from the backend server when responding to server list ping requests?" + ); + + config.set("ping-passthrough.description", description); + config.setComment( + "ping-passthrough.description", + " Should Velocity pass the description from the backend server when responding to server list ping requests?" + ); + + config.set("ping-passthrough.favicon", favicon); + config.setComment( + "ping-passthrough.favicon", + " Should Velocity pass the favicon (also known as the server icon) from the backend server when responding to server list ping requests?" + ); + + config.set("ping-passthrough.modinfo", modinfo); + config.setComment( + "ping-passthrough.modinfo", + " Should Velocity pass the mod list from the backend server when responding to server list ping requests?" + ); + + config.set("config-version", "2.9"); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index 5ccb2b11..97582ca2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -23,6 +23,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.server.PingOptions; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PingPassthroughMode; @@ -100,60 +101,68 @@ public class ServerListPingHandler { CompletableFuture> pingResponses = CompletableFutures.successfulAsList(pings, (ex) -> fallback); - return switch (mode) { - case ALL -> pingResponses.thenApply(responses -> { - // Find the first non-fallback - for (ServerPing response : responses) { - if (response == fallback) { - continue; - } - if (response.getDescriptionComponent() == null) { - return response.asBuilder() - .description(Component.empty()) - .build(); - } + // Return early if ping passthrough is not enabled + if (!mode.enabled()) { + return CompletableFuture.completedFuture(fallback); + } - return response; + return pingResponses.thenApply(responses -> { + // Find the first non-fallback + for (ServerPing response : responses) { + if (response == fallback) { + continue; } - return fallback; - }); - case MODS -> pingResponses.thenApply(responses -> { - // Find the first non-fallback that contains a mod list - for (ServerPing response : responses) { - if (response == fallback) { - continue; - } - Optional modInfo = response.getModinfo(); - if (modInfo.isPresent()) { - return fallback.asBuilder().mods(modInfo.get()).build(); - } - } - return fallback; - }); - case DESCRIPTION -> 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) - ); + ServerPing.Version version; + if (mode.version()) { + version = response.getVersion(); + } else { + version = fallback.getVersion(); } - return fallback; - }); - // Not possible, but covered for completeness. - default -> CompletableFuture.completedFuture(fallback); - }; + + ServerPing.Players players; + if (mode.players()) { + players = response.getPlayers().orElse(null); + } else { + players = fallback.getPlayers().orElse(null); + } + + Component description; + if (mode.description()) { + if (response.getDescriptionComponent() != null) { + description = response.getDescriptionComponent(); + } else { + description = Component.empty(); + } + } else { + description = fallback.getDescriptionComponent(); + } + + Favicon favicon; + if (mode.favicon()) { + favicon = response.getFavicon().orElse(null); + } else { + favicon = fallback.getFavicon().orElse(null); + } + + ModInfo modinfo; + if (mode.modinfo()) { + modinfo = response.getModinfo().orElse(null); + } else { + modinfo = fallback.getModinfo().orElse(null); + } + + return new ServerPing( + version, + players, + description, + favicon, + modinfo + ); + } + return fallback; + }); } /** @@ -168,7 +177,7 @@ public class ServerListPingHandler { ? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION; PingPassthroughMode passthroughMode = configuration.getPingPassthrough(); - if (passthroughMode == PingPassthroughMode.DISABLED) { + if (!passthroughMode.enabled()) { return CompletableFuture.completedFuture(constructLocalPing(shownVersion)); } else { String virtualHostStr = connection.getVirtualHost().map(InetSocketAddress::getHostString) diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml index 0eae2734..34a17f6c 100644 --- a/proxy/src/main/resources/default-velocity.toml +++ b/proxy/src/main/resources/default-velocity.toml @@ -1,5 +1,5 @@ # Config version. Do not change this -config-version = "2.8" +config-version = "2.9" # What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565. bind = "0.0.0.0:25565" @@ -43,7 +43,7 @@ forwarding-secret-file = "forwarding.secret" # 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" +# If your network runs one modpack consistently, consider using ping-passthrough.modinfo = true # instead for a nicer display in the server list. announce-forge = false @@ -51,21 +51,6 @@ announce-forge = false # any existing player who is online if a duplicate connection attempt is made. kick-existing-players = false -# 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. -# The first server in your try list (or forced host) with a mod list will be -# used. If no backend servers can be contacted, Velocity won't display any -# mod information. -# - "description": Uses the description and mod list from the backend server. The first -# server in the try (or forced host) list that responds is used for the -# description and mod list. -# - "all": Uses the backend server's response as the proxy response. The Velocity -# configuration is used if no servers could be contacted. -ping-passthrough = "DISABLED" - # If enabled (default is false), then a sample of the online players on the proxy will be visible # when hovering over the player count in the server list. # This doesn't have any effect when ping passthrough is set to either "description" or "all". @@ -74,6 +59,18 @@ sample-players-in-ping = false # If not enabled (default is true) player IP addresses will be replaced by in logs enable-player-address-logging = true +[ping-passthrough] +# Should Velocity pass the version number from the backend server when responding to server list ping requests? +version = false +# Should Velocity pass the player count from the backend server when responding to server list ping requests? +players = false +# Should Velocity pass the description from the backend server when responding to server list ping requests? +description = false +# Should Velocity pass the favicon (also known as the server icon) from the backend server when responding to server list ping requests? +favicon = false +# Should Velocity pass the mod list from the backend server when responding to server list ping requests? +modinfo = false + [packet-limiter] # Size of the moving time window in seconds used to calculate average rates. # A larger window tolerates short bursts while still enforcing the configured limits over time.