Velocity modern player information forwarding

When working on the fallback server for Nucleoid, one of the required features was to implement Velocity's modern player information forwarding, to allow skins to correctly load in on the fallback server. To do this, I first needed to find information about how the protocol worked, and I struggled to find any official documentation, and so I've summarised my understanding of how it works here.

Initial connection

When the Minecraft client connects to a server, it begins in the handshake state, and immediately sends a packet to the server, specifying its protocol version and the next state it would like to switch to, which can be either status or login. For the purposes of implementing player information forwarding, the status state is not important.

Login phase

When connecting to an online-mode server, the login phase usually involves some cryptography and exchanges with Mojang's servers in order to authenticate the user and enable encryption of the protocol, and this is the point where information such as skin data is loaded by the server. However, when using a proxy like Velocity, the connection between the proxy and the backend server is not encrypted, and the connection is made as if it is to an offline-mode server, which does not involve any communication with Mojang, as the proxy has already performed these steps with the client. This means that the backend server has no access to the player data that velocity obtained, and so a protocol for player information forwarding is required.

Modern player information forwarding

Velocity implements two methods for forwarding player information, the legacy BungeeCord protocol (which is considered insecure), and the newer 'modern forwarding'.

Modern information forwarding makes use of the Login Plugin Request/Response packets during the login phase in order to send the data in a vanilla-compatible way.

To perform modern information forwarding, the backend server must send a Login Plugin Request packet before the Login Success packet, with the channel set to velocity:player_info, and the following payload:

Field Type Value
Protocol version byte 1 for MODERN_FORWARDING_DEFAULT (up to MC 1.18), 2 for MODERN_FORWARDING_WITH_KEY (MC 1.19+)

The velocity proxy should then respond with a corresponding Login Plugin Response, which will contain the player information.

The structure of the packet is the following:

Field Type Description
Signature byte[32] See the Validation section below
Version varint The version of the forwarding protocol the server is using. See above
Client Address String[32767] The IP address of the connecting client, in textual representation
Player ID UUID The Mojang UUID of the connecting player
Username String[16] The Mojang username of the connecting player
Properties Property[] A length (as a varint) prefixed array of Properties (see below)

If the returned version is 2 (MODERN_FORWARDING_WITH_KEY), the following fields are added to the response:

Field Type Description
Public key expiry long The expiry of the players chat signing key
Public key byte[512] The encoded RSA public key used by the client to sign messages
Public key signature byte[4096] The Mojang-provided signature of the key

Properties

Each element in the properties array is a structure like this:

Field Type Description
Name String[32767] The name of the property
Value String[32767] The value of the property
Has signature boolean Whether or not a signature follows
Signature String[32767] The signature for this property

These fields correspond to the way texture data is returned from the Mojang API during regular auth.

Validation

One of the major improvements of modern information forwarding is that the data is signed using a key shared between the proxy and backend server, which prevents somebody from impersonating the proxy if the server is accidentally exposed to the public internet.

The signature is implemented using a HMAC with SHA256 as the hash function. It is calculated from the remaining content of the payload after the signature field, and if it is invalid, then the client should be disconnected immediately.

Thanks

These notes are mostly based on study of the code for the FabricProxy-Lite mod, along with some cross checking with the implementation in the actual Velocity code.