Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding more options for configuring ping passthrough. #1290

Open
wants to merge 16 commits into
base: dev/3.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2018-2023 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;

/**
* Legacy modes for ping passthrough.
*/
public enum LegacyPingPassthroughMode {
DISABLED,
MODS,
DESCRIPTION,
ALL
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,36 @@
package com.velocitypowered.proxy.config;

/**
* Supported passthrough modes for ping passthrough.
* Object to contain all of the things that can be toggled for ping passthrough.
*/
public enum PingPassthroughMode {
DISABLED,
MODS,
DESCRIPTION,
ALL
public class PingPassthroughMode {
public boolean version;
public boolean players;
public boolean description;
public boolean favicon;
public boolean modinfo;

/**
* Passthrough mode constructor.
* Looking at other code, I'm not sure the constructor is supposed to need a javadoc style comment,
* but checkstyle was yelling at me because I didn't include one.
* Probably for the best.
*
* @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 PingPassthroughMode(boolean version, boolean players, boolean description, boolean favicon, boolean modinfo) {
this.version = version;
this.players = players;
this.description = description;
this.favicon = favicon;
this.modinfo = 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 @@ -77,7 +77,9 @@ public class VelocityConfiguration implements ProxyConfig {
@Expose
private boolean onlineModeKickExistingPlayers = false;
@Expose
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
private PingPassthroughMode pingPassthrough = new PingPassthroughMode(false, false, false, false, false);
@Expose
private LegacyPingPassthroughMode legacyPingPassthrough = LegacyPingPassthroughMode.DISABLED;
private final Servers servers;
private final ForcedHosts forcedHosts;
@Expose
Expand Down Expand Up @@ -105,8 +107,9 @@ private VelocityConfiguration(String bind, String motd, int showMaxPlayers, bool
boolean preventClientProxyConnections, boolean announceForge,
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
boolean enablePlayerAddressLogging, Servers servers, ForcedHosts forcedHosts,
Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication) {
LegacyPingPassthroughMode legacyPingPassthrough, boolean enablePlayerAddressLogging,
Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query,
Metrics metrics, boolean forceKeyAuthentication) {
this.bind = bind;
this.motd = motd;
this.showMaxPlayers = showMaxPlayers;
Expand All @@ -117,6 +120,7 @@ private VelocityConfiguration(String bind, String motd, int showMaxPlayers, bool
this.forwardingSecret = forwardingSecret;
this.onlineModeKickExistingPlayers = onlineModeKickExistingPlayers;
this.pingPassthrough = pingPassthrough;
this.legacyPingPassthrough = legacyPingPassthrough;
this.enablePlayerAddressLogging = enablePlayerAddressLogging;
this.servers = servers;
this.forcedHosts = forcedHosts;
Expand Down Expand Up @@ -371,6 +375,10 @@ public PingPassthroughMode getPingPassthrough() {
return pingPassthrough;
}

public LegacyPingPassthroughMode getLegacyPingPassthrough() {
return legacyPingPassthrough;
}

public boolean isPlayerAddressLoggingEnabled() {
return enablePlayerAddressLogging;
}
Expand Down Expand Up @@ -500,9 +508,14 @@ 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 LegacyPingPassthroughMode legacyPingPassthrough = config.getEnumOrElse("ping-passthrough",
LegacyPingPassthroughMode.DISABLED);
final PingPassthroughMode pingPassthrough = new PingPassthroughMode(
config.getOrElse("ping-passthrough-version", false),
config.getOrElse("ping-passthrough-players", false),
config.getOrElse("ping-passthrough-description", false),
config.getOrElse("ping-passthrough-favicon", false),
config.getOrElse("ping-passthrough-modinfo", false));
final String bind = config.getOrElse("bind", "0.0.0.0:25577");
final int maxPlayers = config.getIntOrElse("show-max-players", 500);
final boolean onlineMode = config.getOrElse("online-mode", true);
Expand Down Expand Up @@ -532,7 +545,8 @@ public static VelocityConfiguration read(Path path) throws IOException {
forwardingMode,
forwardingSecret,
kickExisting,
pingPassthroughMode,
pingPassthrough,
legacyPingPassthrough,
enablePlayerAddressLogging,
new Servers(serversConfig),
new ForcedHosts(forcedHostsConfig),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
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.LegacyPingPassthroughMode;
import com.velocitypowered.proxy.config.PingPassthroughMode;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
Expand Down Expand Up @@ -63,7 +65,8 @@ private ServerPing constructLocalPing(ProtocolVersion version) {
}

private CompletableFuture<ServerPing> attemptPingPassthrough(VelocityInboundConnection connection,
PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion) {
PingPassthroughMode mode, LegacyPingPassthroughMode legacyMode,
List<String> servers, ProtocolVersion responseProtocolVersion) {
ServerPing fallback = constructLocalPing(connection.getProtocolVersion());
List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
for (String s : servers) {
Expand All @@ -81,57 +84,118 @@ private CompletableFuture<ServerPing> attemptPingPassthrough(VelocityInboundConn

CompletableFuture<List<ServerPing>> pingResponses = CompletableFutures.successfulAsList(pings,
(ex) -> fallback);
switch (mode) {
case ALL:
return pingResponses.thenApply(responses -> {
// Find the first non-fallback
for (ServerPing response : responses) {
if (response == fallback) {
continue;
}
return response;
// Use the new ping passthrough mode if enabled.
if (mode.enabled()) {
return pingResponses.thenApply(responses -> {
// Find the first non-fallback. If it includes a modlist, add it too.
for (ServerPing response : responses) {
if (response == fallback) {
continue;
}
return fallback;
});
case MODS:
return 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();
}

if (response.getDescriptionComponent() == null) {
continue;
}
return fallback;
});
case DESCRIPTION:
return 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.Version version;
if (mode.version) {
version = response.getVersion();
} else {
version = fallback.getVersion();
}

ServerPing.Players players;
if (mode.players) {
players = response.getPlayers().orElse(null);
} else {
players = fallback.getPlayers().orElse(null);
}

net.kyori.adventure.text.Component description;
if (mode.description) {
description = response.getDescriptionComponent();
} else {
description = fallback.getDescriptionComponent();
}

Favicon favicon;
if (mode.favicon) {
favicon = response.getFavicon().orElse(null);
} else {
favicon = fallback.getFavicon().orElse(null);
}

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

return new ServerPing(
version,
players,
description,
favicon,
modinfo
);
}
return fallback;
});
} else {
// Otherwise, use the legacy ping passthrough mode.
switch (legacyMode) {
case ALL:
return pingResponses.thenApply(responses -> {
// Find the first non-fallback
for (ServerPing response : responses) {
if (response == fallback) {
continue;
}
return response;
}
return fallback;
});
case MODS:
return 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();
}
}
return fallback;
});
case DESCRIPTION:
return 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)
);
}
return fallback;
});
// Not possible, but covered for completeness.
default:
return CompletableFuture.completedFuture(fallback);
}
}
}

Expand All @@ -146,16 +210,17 @@ public CompletableFuture<ServerPing> getInitialPing(VelocityInboundConnection co
ProtocolVersion shownVersion = connection.getProtocolVersion().isSupported()
? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION;
PingPassthroughMode passthroughMode = configuration.getPingPassthrough();
LegacyPingPassthroughMode legacyPassthroughMode = configuration.getLegacyPingPassthrough();

if (passthroughMode == PingPassthroughMode.DISABLED) {
if (!passthroughMode.enabled() && legacyPassthroughMode == LegacyPingPassthroughMode.DISABLED) {
return CompletableFuture.completedFuture(constructLocalPing(shownVersion));
} else {
String virtualHostStr = connection.getVirtualHost().map(InetSocketAddress::getHostString)
.map(str -> str.toLowerCase(Locale.ROOT))
.orElse("");
List<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(
virtualHostStr, server.getConfiguration().getAttemptConnectionOrder());
return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion);
return attemptPingPassthrough(connection, passthroughMode, legacyPassthroughMode, serversToTry, shownVersion);
}
}
}
31 changes: 17 additions & 14 deletions proxy/src/main/resources/default-velocity.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,23 @@ 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"
# For the below options, the backend server Velocity contacts will be the first server that responds in the try list (or the forced host).
# If no servers can be contacted, the Velocity configuration will be used instead.

# Should Velocity send the version number from the backend server when responding to server list ping requests?
ping-passthrough-version = false

# Should Velocity send the player count from the backend server when responding to server list ping requests?
ping-passthrough-players = false

# Should Velocity send the description from the backend server when responding to server list ping requests?
ping-passthrough-description = false

# Should Velocity send the favicon (also known as the server icon) from the backend server when responding to server list ping requests?
ping-passthrough-favicon = false

# Should Velocity send the mod list from the backend server when responding to server list ping requests?
ping-passthrough-modinfo = false

# If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs
enable-player-address-logging = true
Expand Down