Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -513,7 +514,8 @@ public static VelocityConfiguration read(Path path) throws IOException {
new MotdMigration(),
new MiniMessageTranslationsMigration(),
new TransferIntegrationMigration(),
new PacketLimiterMigration()
new PacketLimiterMigration(),
new PingPassthroughMigration(),
};

for (final ConfigurationMigration migration : migrations) {
Expand Down Expand Up @@ -555,9 +557,7 @@ public static VelocityConfiguration read(Path path) throws IOException {
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");
Expand Down Expand Up @@ -590,7 +590,7 @@ public static VelocityConfiguration read(Path path) throws IOException {
forwardingMode,
forwardingSecret,
kickExisting,
pingPassthroughMode,
pingPassthrough,
samplePlayersInPing,
enablePlayerAddressLogging,
new Servers(serversConfig),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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) {
Comment thread
TheMiningTeamYT marked this conversation as resolved.
// 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");
Comment thread
WouterGritter marked this conversation as resolved.

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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -100,60 +101,68 @@ private CompletableFuture<ServerPing> attemptPingPassthrough(VelocityInboundConn

CompletableFuture<List<ServerPing>> 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> modInfo = response.getModinfo();
if (modInfo.isPresent()) {
return fallback.asBuilder().mods(modInfo.get()).build();
}

ServerPing.Version version;
if (mode.version()) {
version = response.getVersion();
} else {
version = fallback.getVersion();
}
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;

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();
}

return new ServerPing(
fallback.getVersion(),
fallback.getPlayers().orElse(null),
response.getDescriptionComponent(),
fallback.getFavicon().orElse(null),
response.getModinfo().orElse(null)
);
Favicon favicon;
if (mode.favicon()) {
favicon = response.getFavicon().orElse(null);
} else {
favicon = fallback.getFavicon().orElse(null);
}
return fallback;
});
// Not possible, but covered for completeness.
default -> CompletableFuture.completedFuture(fallback);
};

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;
});
}

/**
Expand All @@ -168,7 +177,7 @@ public CompletableFuture<ServerPing> getInitialPing(VelocityInboundConnection co
? 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)
Expand Down
Loading