Skip to content

Feature: per-server info forwarding mode#1655

Open
MarcoLvr wants to merge 11 commits intoPaperMC:dev/3.0.0from
MarcoLvr:feature/per-server-info-forwarding-mode
Open

Feature: per-server info forwarding mode#1655
MarcoLvr wants to merge 11 commits intoPaperMC:dev/3.0.0from
MarcoLvr:feature/per-server-info-forwarding-mode

Conversation

@MarcoLvr
Copy link

@MarcoLvr MarcoLvr commented Sep 22, 2025

What's this?
With this edit every server can use a different forwarding mode to obtain and forward player info. For instance, if you are running a 1.12 (or lower version) server on a velocity proxy with MODERN player info forwarding the server doesn't support MODERN forwarding. So you can set LEGACY forwarding mode for that server and velocity will use ONLY FOR THAT SERVER the legacy forwarding mode. The default mode is "INHERIT" which uses the player info forwarding mode set in the config.

Note: the INHERIT mode doesn't physically exists. Just set the server forwarding mode to NULL or avoid valuing it in the config

The main configuration now supports two syntaxes for describing a server:

classic syntax (INHERIT mode): server = "address:port"
new syntax: server = {address = "address:port", forwarding-mode = "MODE"}

all servers created without defining a forwarding mode will use the INHERIT mode.

Example of use
Imagine a simple network with a lobby, a 1.8 game mode (like bedwars) and maybe a Fabric 1.21 server.
Fabric needs MODERN forwarding but the 1.8 server cannot use it. So you have two choices:

  1. Set the main forwarding mode to legacy (allow <1.13 players to join) and set MODERN mode only for the fabric server.
[...]
player-info-forwarding-mode = "legacy"
[...]
[servers]
try = ["lobby"]
lobby = "localhost:25566" //we leave this in INHERIT mode because in this case the lobby could be a 1.8 server too
bedwars = "localhost:25567" //we leave this in INHERIT mode because the main mode is legacy
modded =  { address = "localhost:25568", forwarding-mode = "MODERN" } //fabric needs MODERN forwarding
[...]
  1. Set the main forwarding mode to MODERN and set LEGACY mode only for the 1.8 server.
[...]
player-info-forwarding-mode = "modern"
[...]
[servers]
try = ["lobby"]
lobby = "localhost:25566" //we leave this in INHERIT mode because in this case the lobby could be a >1.13 server
bedwars = { address = "localhost:25567", forwarding-mode = "LEGACY" }  //bedwars (1.8 server) needs legacy mode
modded =  "localhost:25568" //we leave this in INHERIT mode because the main mode is modern
[...]

This system could be very useful for these types of situations!

What's this?
Every server can use a different forwarding mode to obtain and forward player info.
For instance, if you are running a 1.12 (or lower version) server on a velocity proxy with MODERN player info forwarding the server doesn't support MODERN forwarding. So you need to set LEGACY forwarding mode for that server and velocity will use ONLY FOR THAT SERVER the legacy forwarding mode. The default mode is "FOLLOWUP" mode which uses the player info forwarding mode set in the config.

The main configuration now supports two syntaxes for describing a server:

classic syntax (FOLLOWUP mode): server = "address:port"
new syntax: server = {address = "address:port", forwarding-mode = "MODE"}

all servers created without defining a forwarding mode will use the FOLLOWUP mode.
@4drian3d 4drian3d added the type: feature New feature or request label Sep 22, 2025
…Servers() method to avoid breaking compatibility with current versions.

ServerInfoForwardingMode.java: added a javadoc for the FOLLOWUP mode and added the NONE mode

VelocityConfiguration.java: made "mode" field optional for the new server syntax. Restored old forwarding secret check.

VelocityRegisteredServer.java: added the forwarding secret check if forwarding mode is MODERN or BUNGEEGUARD

ServerForwardingModeUtil.java: mapped NONE mode to NONE mode in PlayerInfoForwarding.java
@MarcoLvr MarcoLvr requested a review from 4drian3d September 22, 2025 23:21
…guration#validate

added requiresNonNull checks on BackendServerConfigImpl
@MarcoLvr MarcoLvr requested a review from 4drian3d September 23, 2025 00:14
…rverConfig. Made address and forwardingMode final in its implementation class.

deprecated getServers() in favor of getBackendServers()

Fixed proxy option syntax for "add-server". Now the syntax is: <name>:<host>:[port]:[forwardingmode]

moved ServerInfoForwardingMode conversion from util package to VelocityRegisteredServer#getConfiguredPlayerInfoForwarding
@MarcoLvr MarcoLvr requested a review from Emilxyz September 23, 2025 12:53
@MarcoLvr
Copy link
Author

did everything! Let me know if there's any other change to do

…mentation class in the proxy module.

Modified comment in ProxyConfig#getServers
@MarcoLvr MarcoLvr requested a review from 4drian3d September 23, 2025 23:04
Copy link
Contributor

@4drian3d 4drian3d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@4drian3d 4drian3d linked an issue Sep 24, 2025 that may be closed by this pull request
@MarcoLvr
Copy link
Author

@Emilxyz we are waiting for you :)

Copy link
Contributor

@Emilxyz Emilxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are waiting for you

I'm not a team member, just sharing my thoughts, so my approval is not actually required. Also a friendly reminder that everyone is doing this on their free time, so it's not really needed to bump a review request after only a day.

I gave this another look and it basically lgtm other than these minor things in review comments.

Another thought I had (but I would wait with doing any changes) is that maybe we can just use ServerInfo for this instead of adding a new type, they basically share the same fields and probably will continue to do so in the future as well.

Also there is the issue of the planned config api switch which is holding up other things already that would make changes to the config, so be prepared for this pr to also be affected by this

Copy link
Member

@kennytv kennytv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving general approval, but notes for whoever wants to merge or consider this:

  • There is also #1357
  • There is a PR to expose the forwarding mode that wasn't merged since it's deemed more of an internal property

Copilot AI review requested due to automatic review settings February 20, 2026 21:16
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces per-server player info forwarding mode configuration, allowing each backend server to use a different forwarding mode (MODERN, LEGACY, BUNGEEGUARD, or NONE) instead of inheriting the global proxy-wide setting. This enables mixed-version networks where some servers (e.g., 1.12) require LEGACY forwarding while others (e.g., Fabric 1.21) require MODERN forwarding.

Changes:

  • Added new ServerInfoForwardingMode enum and BackendServerConfig record to the API for per-server configuration
  • Extended configuration syntax to support both classic string format (server = "address:port") and new object format (server = {address = "...", forwarding-mode = "..."})
  • Modified ServerInfo to include an optional forwarding mode field, with corresponding updates to equals/hashCode/toString methods

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfoForwardingMode.java New enum defining per-server forwarding modes (MODERN, BUNGEEGUARD, LEGACY, NONE)
api/src/main/java/com/velocitypowered/api/proxy/config/BackendServerConfig.java New record to hold backend server configuration including address and optional forwarding mode
api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java Added forwarding mode field, new constructor, getter method, and updated equals/hashCode/toString
api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java Added getBackendServers() method and deprecated getServers()
proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java Updated server parsing to support both old and new config formats, added per-server forwarding secret validation
proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java Added getConfiguredPlayerInfoForwarding() to resolve effective forwarding mode
proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java Changed to use per-server forwarding mode instead of global setting
proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java Updated to use per-server forwarding mode in login flow
proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java Updated server registration to include forwarding mode
proxy/src/main/java/com/velocitypowered/proxy/ProxyOptions.java Extended command-line server parsing to support forwarding mode parameter
proxy/src/main/resources/default-velocity.toml Updated example configuration to demonstrate new syntax
Comments suppressed due to low confidence (4)

api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfoForwardingMode.java:19

  • The enum constant order in ServerInfoForwardingMode is inconsistent with PlayerInfoForwarding. ServerInfoForwardingMode orders them as MODERN, BUNGEEGUARD, LEGACY, NONE while PlayerInfoForwarding uses NONE, LEGACY, BUNGEEGUARD, MODERN. For consistency and to avoid confusion when developers work with both enums, consider using the same ordering as PlayerInfoForwarding since these represent the same concepts.
public enum ServerInfoForwardingMode {
    MODERN,
    BUNGEEGUARD,
    LEGACY,
    NONE

proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java:660

  • The configuration parser silently ignores unknown keys in the server configuration object. If a user makes a typo like "forwaring-mode" or "forwarding_mode", the configuration will load without error but the forwarding mode will be null (inheriting from global config). This could lead to unexpected behavior. Consider logging a warning for unknown configuration keys within server entries to help users identify typos or deprecated options.
            for (UnmodifiableConfig.Entry entry2 : c.entrySet()) {
              if (entry2.getKey().equalsIgnoreCase("address")) {
                address = entry2.getValue();
              }
              if (entry2.getKey().equalsIgnoreCase("forwarding-mode")) {
                forwardingMode = ServerInfoForwardingMode.valueOf(ServerInfoForwardingMode.class, entry2.getValue());
              }
            }

api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfoForwardingMode.java:21

  • There are two blank lines at the end of the file instead of one. This is inconsistent with typical Java file formatting conventions which usually have exactly one blank line at the end of the file.
    proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java:82
  • The javadoc has an extra blank line between the two @param tags (line 81), which is inconsistent with standard javadoc formatting where @param tags should be consecutive without blank lines between them.
   * @param server velocity instance
   *
   * @param serverInfo info of the backend server

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +190 to +191
logger.error("You don't have a forwarding secret set. This is required if "
+ "you are using MODERN or BUNGEEGUARD forwarding modes.");
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says "You don't have a forwarding secret set. This is required if you are using MODERN or BUNGEEGUARD forwarding modes." but it doesn't specify which server has the problematic configuration. Consider including the server name (entry.getKey()) in the error message to help administrators identify which specific server needs attention. For example: "Server 'servername' uses MODERN/BUNGEEGUARD forwarding mode but you don't have a forwarding secret set."

Suggested change
logger.error("You don't have a forwarding secret set. This is required if "
+ "you are using MODERN or BUNGEEGUARD forwarding modes.");
logger.error("Server '{}' uses MODERN or BUNGEEGUARD forwarding mode but you "
+ "don't have a forwarding secret set. This is required for security.",
entry.getKey());

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +82
factions = {address = "127.0.0.1:30067", forwarding-mode = "MODERN"}
minigames = {address = "127.0.0.1:30068", forwarding-mode = "LEGACY"}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example values in the default configuration show forwarding modes that may not align with a typical setup. Setting "factions" to MODERN and "minigames" to LEGACY seems arbitrary. Consider either using all three servers with null/INHERIT mode (demonstrating the default behavior), or adding comments to explain why different modes are shown. This will help users understand when and why they should set per-server forwarding modes rather than just copying these examples.

Copilot uses AI. Check for mistakes.
Comment on lines 109 to 141
@@ -125,7 +138,7 @@ public Class<? extends ServerInfo> valueType() {

@Override
public String valuePattern() {
return "name>:<address";
return "name>:<host>:[port]:[forwardingmode]";
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The valuePattern indicates optional components with brackets "[port]:[forwardingmode]", but the conversion logic at line 109 splits on ":" with a limit of 4, expecting exactly 4 parts for the full format. The description says "Use ::[port]:[forwardingmode]" which suggests port and forwardingmode are optional, but the actual parsing logic requires that if you want to specify forwardingmode, you must include all 4 parts (name, host, port, forwardingmode). The pattern and error message should clarify this or the parsing should be made more flexible to handle name:host:forwardingmode without requiring the port.

Copilot uses AI. Check for mistakes.
Comment on lines 99 to +106
return Objects.equals(name, that.name)
&& Objects.equals(address, that.address);
&& Objects.equals(address, that.address)
&& Objects.equals(forwardingMode, that.forwardingMode);
}

@Override
public int hashCode() {
return Objects.hash(name, address);
return Objects.hash(name, address, forwardingMode);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change to include forwardingMode in equals() and hashCode() is a breaking change in the API. Previously, two ServerInfo objects with the same name and address but different (or null vs non-null) forwardingMode would be considered equal. Now they won't be. This could break existing code that relies on ServerInfo equality for lookups, comparisons, or use in collections. While this change makes logical sense for the new feature, it should be clearly documented as a breaking change or reconsidered to maintain backward compatibility. Consider whether forwardingMode should be part of the identity of a ServerInfo object or just a configuration detail.

Copilot uses AI. Check for mistakes.
address = entry2.getValue();
}
if (entry2.getKey().equalsIgnoreCase("forwarding-mode")) {
forwardingMode = ServerInfoForwardingMode.valueOf(ServerInfoForwardingMode.class, entry2.getValue());
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The valueOf call has several issues: 1) It uses the two-parameter Enum.valueOf(Class, String) method but should use the simpler ServerInfoForwardingMode.valueOf(String) method for consistency with line 126 in ProxyOptions.java. 2) The value is not converted to uppercase before calling valueOf, making configuration case-sensitive. Users writing "modern" or "legacy" will get an IllegalArgumentException. 3) There's no try-catch block to handle invalid values gracefully. The code should convert the value to uppercase using entry2.getValue().toString().toUpperCase() and wrap the valueOf call in a try-catch block with a meaningful error message including the server name.

Suggested change
forwardingMode = ServerInfoForwardingMode.valueOf(ServerInfoForwardingMode.class, entry2.getValue());
Object rawValue = entry2.getValue();
if (rawValue != null) {
String modeValue = rawValue.toString().toUpperCase();
try {
forwardingMode = ServerInfoForwardingMode.valueOf(modeValue);
} catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(
"Invalid forwarding-mode '" + rawValue + "' for server '" + entry.getKey() + "'", ex);
}
}

Copilot uses AI. Check for mistakes.
*/
public PlayerInfoForwarding getConfiguredPlayerInfoForwarding() {
if (serverInfo.getServerInfoForwardingMode() == null) {
return server.getConfiguration().getPlayerInfoForwardingMode();
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method has a potential NullPointerException at line 101. If serverInfo.getServerInfoForwardingMode() returns null (line 100 check), then server.getConfiguration() is called. However, the server field is annotated with @Nullable and could be null. When VelocityRegisteredServer is constructed with a null server (which is allowed by the constructor), calling getConfiguration() on it will throw a NullPointerException. Consider adding a null check for server or documenting that this method requires a non-null server to function properly.

Suggested change
return server.getConfiguration().getPlayerInfoForwardingMode();
VelocityServer velocityServer = requireNonNull(server, "server");
return velocityServer.getConfiguration().getPlayerInfoForwardingMode();

Copilot uses AI. Check for mistakes.
Comment on lines 78 to 79
# Configure your servers here. Each key represents the server's name, and the value
# represents the IP address of the server to connect to.
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on lines 78-79 doesn't explain the new per-server forwarding mode feature. Users seeing the new syntax for the first time won't understand what "forwarding-mode" means or when they should use it. Consider expanding the comment to briefly mention both syntaxes and that forwarding-mode is optional, with a reference to the documentation or a note that null/omitted values inherit from the global player-info-forwarding-mode setting.

Suggested change
# Configure your servers here. Each key represents the server's name, and the value
# represents the IP address of the server to connect to.
# Configure your servers here. Each key represents the server's name.
# You can use the short form, where the value is the IP address and port to connect to:
# lobby = "127.0.0.1:30066"
# or the object form:
# factions = { address = "127.0.0.1:30067", forwarding-mode = "MODERN" }
# In the object form, address is the IP/port and forwarding-mode is optional; if it is
# omitted or set to null, the server inherits the global player-info-forwarding-mode
# setting defined above. See the Velocity documentation for more details.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-server forwarding mode

6 participants