Skip to content
Open
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 @@ -25,6 +25,7 @@ public class PlayerHandshakeEvent extends Event implements Cancellable {

private static final HandlerList HANDLER_LIST = new HandlerList();

private final UUID connectionId;
private final String originalHandshake;
private final String originalSocketAddressHostname;
private @Nullable String serverHostname;
Expand All @@ -42,9 +43,16 @@ public PlayerHandshakeEvent(final String originalHandshake, final boolean cancel
this(originalHandshake, "127.0.0.1", cancelled);
}

@Deprecated
@ApiStatus.Internal
public PlayerHandshakeEvent(final String originalHandshake, final String originalSocketAddressHostname, final boolean cancelled) {
this(UUID.randomUUID(), originalHandshake, originalSocketAddressHostname, cancelled);
}

@ApiStatus.Internal
public PlayerHandshakeEvent(final UUID connectionId, final String originalHandshake, final String originalSocketAddressHostname, final boolean cancelled) {
super(true);
this.connectionId = connectionId;
this.originalHandshake = originalHandshake;
this.originalSocketAddressHostname = originalSocketAddressHostname;
this.cancelled = cancelled;
Expand All @@ -59,6 +67,19 @@ public String getOriginalHandshake() {
return this.originalHandshake;
}

/**
* Gets the stable connection identifier associated with this handshake.
*
* <p>This identifier remains the same throughout the entire connection
* lifecycle and can be used to correlate this handshake with subsequent
* connection stages such as login and join.</p>
*
* @return the stable connection identifier
*/
public UUID getConnectionId() {
return this.connectionId;
}

/**
* Gets the original socket address hostname.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,16 @@ public class PaperServerListPingEvent extends ServerListPingEvent implements Can
private Object[] players;

@ApiStatus.Internal
@Deprecated(forRemoval = true)
public PaperServerListPingEvent(@NotNull StatusClient client, @NotNull net.kyori.adventure.text.Component motd, int numPlayers, int maxPlayers,
@NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon) {
super("", client.getAddress().getAddress(), motd, numPlayers, maxPlayers);
this(UUID.randomUUID(), client, motd, numPlayers, maxPlayers, version, protocolVersion, favicon);
}

@ApiStatus.Internal
public PaperServerListPingEvent(@NotNull UUID connectionId, @NotNull StatusClient client, @NotNull net.kyori.adventure.text.Component motd, int numPlayers, int maxPlayers,
@NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon) {
super(connectionId, "", client.getAddress().getAddress(), motd, numPlayers, maxPlayers);
this.client = client;
this.numPlayers = numPlayers;
this.version = version;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.Nullable;

Expand Down Expand Up @@ -62,4 +63,19 @@ public interface PlayerConnection {
* @return the player's proxy address, null if the server doesn't have Proxy Protocol enabled, or the player didn't connect to an HAProxy instance
*/
@Nullable InetSocketAddress getHAProxyAddress();

/**
* Gets a stable identifier for this connection that remains the same
* throughout its entire lifecycle, from the initial handshake until the
* connection is closed or the player fully joins the server.
*
* <p>This identifier is unique per connection and can be used to reliably
* correlate data collected during earlier connection stages (e.g. handshake
* or login) with the player that eventually joins the server, without
* relying on the client's IP address.</p>
*
* @return the stable connection identifier
*/
UUID getConnectionId();

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.base.Preconditions;
import java.net.InetAddress;
import java.util.UUID;
import java.util.Iterator;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
Expand Down Expand Up @@ -32,28 +33,19 @@ public class ServerListPingEvent extends ServerEvent implements Iterable<Player>
private final int numPlayers;
private Component motd;
private int maxPlayers;
private final UUID connectionId;

@ApiStatus.Internal
@Deprecated(forRemoval = true)
public ServerListPingEvent(@NotNull final String hostname, @NotNull final InetAddress address, @NotNull final String motd, final int numPlayers, final int maxPlayers) {
super(true);
this(UUID.randomUUID(), hostname, address, LegacyComponentSerializer.legacySection().deserialize(motd), numPlayers, maxPlayers);
Preconditions.checkArgument(numPlayers >= 0, "Cannot have negative number of players online", numPlayers);
this.hostname = hostname;
this.address = address;
this.motd = LegacyComponentSerializer.legacySection().deserialize(motd);
this.numPlayers = numPlayers;
this.maxPlayers = maxPlayers;
}

@ApiStatus.Internal
@Deprecated(forRemoval = true)
protected ServerListPingEvent(@NotNull final String hostname, @NotNull final InetAddress address, @NotNull final String motd, final int maxPlayers) {
super(true);
this.numPlayers = MAGIC_PLAYER_COUNT;
this.hostname = hostname;
this.address = address;
this.motd = LegacyComponentSerializer.legacySection().deserialize(motd);
this.maxPlayers = maxPlayers;
this(UUID.randomUUID(), hostname, address, LegacyComponentSerializer.legacySection().deserialize(motd), maxPlayers);
}

@ApiStatus.Internal
Expand All @@ -63,8 +55,15 @@ public ServerListPingEvent(@NotNull final InetAddress address, @NotNull final Co
}

@ApiStatus.Internal
@Deprecated(forRemoval = true)
public ServerListPingEvent(@NotNull final String hostname, @NotNull final InetAddress address, @NotNull final Component motd, final int numPlayers, final int maxPlayers) {
this(UUID.randomUUID(), hostname, address, motd, numPlayers, maxPlayers);
}

@ApiStatus.Internal
public ServerListPingEvent(@NotNull final UUID connectionId, @NotNull final String hostname, @NotNull final InetAddress address, @NotNull final Component motd, final int numPlayers, final int maxPlayers) {
super(true);
this.connectionId = connectionId;
this.hostname = hostname;
this.address = address;
this.motd = motd;
Expand All @@ -84,8 +83,15 @@ protected ServerListPingEvent(@NotNull final InetAddress address, @NotNull final
* count.
*/
@ApiStatus.Internal
@Deprecated(forRemoval = true)
protected ServerListPingEvent(final @NotNull String hostname, final @NotNull InetAddress address, final @NotNull Component motd, final int maxPlayers) {
this(UUID.randomUUID(), hostname, address, motd, maxPlayers);
}

@ApiStatus.Internal
protected ServerListPingEvent(final @NotNull UUID connectionId, final @NotNull String hostname, final @NotNull InetAddress address, final @NotNull Component motd, final int maxPlayers) {
this.numPlayers = MAGIC_PLAYER_COUNT;
this.connectionId = connectionId;
this.hostname = hostname;
this.address = address;
this.motd = motd;
Expand All @@ -103,6 +109,20 @@ public String getHostname() {
return this.hostname;
}

/**
* Gets the stable connection identifier associated with this ping.
*
* <p>This identifier remains the same throughout the entire connection
* lifecycle and can be used to correlate this ping with subsequent
* connection stages such as handshake, login and join.</p>
*
* @return the stable connection identifier
*/
@NotNull
public UUID getConnectionId() {
return this.connectionId;
}

/**
* Get the address the ping is coming from.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Umbre11as <olegumbre11a@yandex.ru>
Date: Sun, 30 Aug 2026 05:00:22 +0300
Subject: [PATCH] Add stable connection identifier API


diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 9fbc7e93712672dbab67f931e2aee447d803607b..e952765eb0e9a5a41b6b5948a9b3b85f4d4b2b49 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -93,6 +93,13 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
public java.net.InetSocketAddress virtualHost;
private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); // Paper - Disable explicit connection flushing
// Paper end
+ // Paper start - stable connection identifier
+ private final UUID connectionId = UUID.randomUUID();
+
+ public UUID getConnectionId() {
+ return this.connectionId;
+ }
+ // Paper end - stable connection identifier
// Paper start - add utility methods
public final net.minecraft.server.level.ServerPlayer getPlayer() {
if (this.packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl impl) {
diff --git a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
index 649f9646abb1bd84f1f22409aee09d2a4d27e581..4826053a4bd2c2449d70d64170262cfbc2125b16 100644
--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
@@ -122,7 +122,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL
if (com.destroystokyo.paper.event.player.PlayerHandshakeEvent.getHandlerList().getRegisteredListeners().length != 0) { // Hello? Can you hear me?
java.net.SocketAddress socketAddress = this.connection.getRemoteAddress();
String hostnameOfRemote = socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getHostString() : java.net.InetAddress.getLoopbackAddress().getHostAddress();
- com.destroystokyo.paper.event.player.PlayerHandshakeEvent event = new com.destroystokyo.paper.event.player.PlayerHandshakeEvent(packet.hostName(), hostnameOfRemote, !proxyLogicEnabled);
+ com.destroystokyo.paper.event.player.PlayerHandshakeEvent event = new com.destroystokyo.paper.event.player.PlayerHandshakeEvent(this.connection.getConnectionId(), packet.hostName(), hostnameOfRemote, !proxyLogicEnabled);
if (event.callEvent()) {
// If we've failed somehow, let the client know so and go no further.
if (event.isFailed()) {
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.apache.commons.lang3.StringUtils;

import java.net.InetSocketAddress;
import java.util.UUID;

import javax.annotation.Nullable;

Expand Down Expand Up @@ -46,7 +47,7 @@ public boolean isLegacy() {
public static PaperServerListPingEvent processRequest(MinecraftServer server,
InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) {

PaperServerListPingEvent event = new PaperServerListPingEventImpl(server,
PaperServerListPingEvent event = new PaperServerListPingEventImpl(UUID.randomUUID(), server,
new PaperLegacyStatusClient(address, protocolVersion, virtualHost), Byte.MAX_VALUE, null);
server.server.getPluginManager().callEvent(event);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
import org.bukkit.entity.Player;
import org.bukkit.util.CachedServerIcon;

import java.util.UUID;
import javax.annotation.Nullable;

class PaperServerListPingEventImpl extends PaperServerListPingEvent {

private final MinecraftServer server;

PaperServerListPingEventImpl(MinecraftServer server, StatusClient client, int protocolVersion, @Nullable CachedServerIcon icon) {
super(client, server.motd(), server.getPlayerCount(), server.getMaxPlayers(),
PaperServerListPingEventImpl(UUID connectionId, MinecraftServer server, StatusClient client, int protocolVersion, @Nullable CachedServerIcon icon) {
super(connectionId, client, server.motd(), server.getPlayerCount(), server.getMaxPlayers(),
server.getServerModName() + ' ' + server.getServerVersion(), protocolVersion, icon);
this.server = server;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public final class StandardPaperServerListPingEventImpl extends PaperServerListP
private List<NameAndId> originalSample;

private StandardPaperServerListPingEventImpl(MinecraftServer server, Connection connection, ServerStatus ping) {
super(server, new PaperStatusClient(connection), ping.version().map(ServerStatus.Version::protocol).orElse(-1), server.server.getServerIcon());
super(connection.getConnectionId(), server, new PaperStatusClient(connection), ping.version().map(ServerStatus.Version::protocol).orElse(-1), server.server.getServerIcon());
this.originalSample = ping.players().map(ServerStatus.Players::sample).orElse(null); // GH-1473 - pre-tick race condition NPE
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.minecraft.network.protocol.common.ClientboundCustomReportDetailsPacket;
import net.minecraft.network.protocol.common.ClientboundServerLinksPacket;
Expand Down Expand Up @@ -105,6 +106,11 @@ public InetSocketAddress getClientAddress() {
return this.packetListener.connection.haProxyAddress instanceof final InetSocketAddress inetSocketAddress ? inetSocketAddress : null;
}

@Override
public UUID getConnectionId() {
return this.packetListener.connection.getConnectionId();
}

@Override
public void storeCookie(final NamespacedKey key, final byte[] value) {
Preconditions.checkArgument(key != null, "Cookie key cannot be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.papermc.paper.adventure.PaperAdventure;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.minecraft.server.network.ServerLoginPacketListenerImpl;
import org.jspecify.annotations.NullMarked;
Expand Down Expand Up @@ -66,4 +67,10 @@ public void disconnect(final Component component) {
public boolean isConnected() {
return this.packetListener.connection.isConnected();
}

@Override
public UUID getConnectionId() {
return this.packetListener.connection.getConnectionId();
}

}
Loading