Skip to main content

Plugin Messaging

First introduced in 2012, plugin messaging is a way for Velocity plugins to communicate with clients and backend servers.

Velocity manages connections in both directions, for both the client and backend server. This means Velocity plugins need to consider 4 main cases:

warning

When listening to PluginMessageEvent, ensure the result is ForwardResult.handled() if you do not intend the client to participate.

If the result is forwarded, players can impersonate the proxy to your backend servers.

Additionally, ensure the result is set correctly after actually handling correct messages, to prevent them from being leaked to the other party.

This can be achieved with unconditionally setting the result between checking the identifier and checking the source, as shown in the examples.

Additionally, BungeeCord channel compatibility is included, which may remove the need for a companion Velocity plugin in certain cases.

Case 1: Receiving a plugin message from a player

This is for when you need to handle or inspect the contents of a plugin message sent by a player. It will require registering with the ChannelRegistrar for the event to be fired.

An example use case could be logging messages from a mod that reports the enabled features.

public static final MinecraftChannelIdentifier IDENTIFIER = MinecraftChannelIdentifier.from("custom:main");

@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
proxyServer.getChannelRegistrar().register(IDENTIFIER);
}

@Subscribe
public void onPluginMessageFromPlayer(PluginMessageEvent event) {
// Check if the identifier matches first, no matter the source.
if (!IDENTIFIER.equals(event.getIdentifier())) {
return;
}

// mark PluginMessage as handled, indicating that the contents
// should not be forwarding to their original destination.
event.setResult(PluginMessageEvent.ForwardResult.handled());

// Alternatively:

// mark PluginMessage as forwarded, indicating that the contents
// should be passed through, as if Velocity is not present.
//event.setResult(PluginMessageEvent.ForwardResult.forward());

// only attempt parsing the data if the source is a player
if (!(event.getSource() instanceof Player player)) {
return;
}

ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
// handle packet data
}

Case 2: Sending a plugin message to a backend server

This is for when you need to send a plugin message to a backend server.

There are two methods to send a plugin message to the backend, depending on what you need to achieve.

warning

On your backend server, only listen for plugin messages if you are sure only a trusted proxy can send them to your server.

Otherwise, a player can pretend to be your proxy, and spoof them.

Using any connected player

This is useful if you just want to communicate something relevant to the entire server, or otherwise can be derived from its content.

An example use case could be telling the server to shut down.

public boolean sendPluginMessageToBackend(RegisteredServer server, ChannelIdentifier identifier, byte[] data) {
// On success, returns true
return server.sendPluginMessage(identifier, data);
}

Using a specific player's connection

This is useful if you want to communicate something about a specific player to their current backend server. You may want additional checks to ensure it will be handled correctly on the backend the player is on.

An example use case could be telling the backend server to give the player a specific item.

public boolean sendPluginMessageToBackendUsingPlayer(Player player, ChannelIdentifier identifier, byte[] data) {
Optional<ServerConnection> connection = player.getCurrentServer();
if (connection.isPresent()) {
// On success, returns true
return connection.get().sendPluginMessage(identifier, data);
}
return false;
}

Case 3: Receiving a plugin message from a backend server

This is for when you need to receive plugin messages from your backend server. It will require registering with the ChannelRegistrar for the event to be fired.

An example use case could be handing a request to transfer the player to another server.

public static final MinecraftChannelIdentifier IDENTIFIER = MinecraftChannelIdentifier.from("custom:main");

@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
proxyServer.getChannelRegistrar().register(IDENTIFIER);
}

@Subscribe
public void onPluginMessageFromBackend(PluginMessageEvent event) {
// Check if the identifier matches first, no matter the source.
// this allows setting all messages to IDENTIFIER as handled,
// preventing any client-originating messages from being forwarded.
if (!IDENTIFIER.equals(event.getIdentifier())) {
return;
}

// mark PluginMessage as handled, indicating that the contents
// should not be forwarding to their original destination.
event.setResult(PluginMessageEvent.ForwardResult.handled());

// Alternatively:

// mark PluginMessage as forwarded, indicating that the contents
// should be passed through, as if Velocity is not present.
//
// this should be used with extreme caution,
// as any client can freely send whatever it wants, pretending to be the proxy
//event.setResult(PluginMessageEvent.ForwardResult.forward());

// only attempt parsing the data if the source is a backend server
if (!(event.getSource() instanceof ServerConnection backend)) {
return;
}

ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
// handle packet data
}

Case 4: Sending a plugin message to a player

This is for when you need to send a plugin message to a player.

tip

This is only really useful for when you are making client-side mods. Otherwise, the player likely will just ignore the message.

public boolean sendPluginMessageToPlayer(Player player, ChannelIdentifier identifier, byte[] data) {
// On success, returns true
return player.sendPluginMessage(identifier, data);
}

BungeeCord channel compatibility

This allows your backend servers to communicate with Velocity in a way compatible with BungeeCord.

By default, your Velocity server will respond to the bungeecord:main channel, if bungee-plugin-message-channel is enabled in the configuration.

The "bungeecord" specification

See here for a list of all the built-in plugin messages that BungeeCord / Velocity supports.

Written for version: 1.21