mirror of
https://github.com/PaperMC/Paper.git
synced 2025-08-31 12:23:51 -07:00
server/network
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
--- a/net/minecraft/server/network/LegacyQueryHandler.java
|
||||
+++ b/net/minecraft/server/network/LegacyQueryHandler.java
|
||||
@@ -14,6 +_,7 @@
|
||||
public class LegacyQueryHandler extends ChannelInboundHandlerAdapter {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ServerInfo server;
|
||||
+ private ByteBuf buf; // Paper
|
||||
|
||||
public LegacyQueryHandler(ServerInfo server) {
|
||||
this.server = server;
|
||||
@@ -22,6 +_,17 @@
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext context, Object message) {
|
||||
ByteBuf byteBuf = (ByteBuf)message;
|
||||
+ // Paper start - Make legacy ping handler more reliable
|
||||
+ if (this.buf != null) {
|
||||
+ try {
|
||||
+ readLegacy1_6(context, byteBuf);
|
||||
+ } finally {
|
||||
+ byteBuf.release();
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - Make legacy ping handler more reliable
|
||||
+
|
||||
byteBuf.markReaderIndex();
|
||||
boolean flag = true;
|
||||
|
||||
@@ -33,9 +_,21 @@
|
||||
|
||||
SocketAddress socketAddress = context.channel().remoteAddress();
|
||||
int i = byteBuf.readableBytes();
|
||||
+ String string = null; // Paper
|
||||
+ // org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketAddress, this.server.getMotd(), this.server.getPlayerCount(), this.server.getMaxPlayers()); // CraftBukkit // Paper
|
||||
+ com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper
|
||||
if (i == 0) {
|
||||
- LOGGER.debug("Ping: (<1.3.x) from {}", socketAddress);
|
||||
- String string = createVersion0Response(this.server);
|
||||
+ LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress: "<ip address withheld>"); // Paper - Respect logIPs option
|
||||
+ // Paper start - Call PaperServerListPingEvent and use results
|
||||
+ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 39, null);
|
||||
+ if (event == null) {
|
||||
+ context.close();
|
||||
+ byteBuf.release();
|
||||
+ flag = false;
|
||||
+ return;
|
||||
+ }
|
||||
+ string = String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers());
|
||||
+ // Paper end - Call PaperServerListPingEvent and use results
|
||||
sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
|
||||
} else {
|
||||
if (byteBuf.readUnsignedByte() != 1) {
|
||||
@@ -43,16 +_,35 @@
|
||||
}
|
||||
|
||||
if (byteBuf.isReadable()) {
|
||||
- if (!readCustomPayloadPacket(byteBuf)) {
|
||||
+ // Paper start - Replace with improved version below
|
||||
+ if (byteBuf.readUnsignedByte() != LegacyProtocolUtils.CUSTOM_PAYLOAD_PACKET_ID) {
|
||||
+ string = this.readLegacy1_6(context, byteBuf);
|
||||
+ if (string == null) {
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ // if (!readCustomPayloadPacket(byteBuf)) {
|
||||
+ // return;
|
||||
+ // }
|
||||
+
|
||||
+ // LOGGER.debug("Ping: (1.6) from {}", socketAddress);
|
||||
+ // Paper end - Replace with improved version below
|
||||
+ } else {
|
||||
+ LOGGER.debug("Ping: (1.4-1.5.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress : "<ip address withheld>"); // Paper - Respect logIPs option
|
||||
+ }
|
||||
+
|
||||
+ if (string == null) {
|
||||
+ // Paper start - Call PaperServerListPingEvent and use results
|
||||
+ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 127, null); // Paper
|
||||
+ if (event == null) {
|
||||
+ context.close();
|
||||
+ byteBuf.release();
|
||||
+ flag = false;
|
||||
return;
|
||||
}
|
||||
-
|
||||
- LOGGER.debug("Ping: (1.6) from {}", socketAddress);
|
||||
- } else {
|
||||
- LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketAddress);
|
||||
+ string = String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit
|
||||
+ // Paper end - Call PaperServerListPingEvent and use results
|
||||
}
|
||||
-
|
||||
- String string = createVersion1Response(this.server);
|
||||
sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
|
||||
}
|
||||
|
||||
@@ -95,21 +_,97 @@
|
||||
}
|
||||
}
|
||||
|
||||
- private static String createVersion0Response(ServerInfo server) {
|
||||
- return String.format(Locale.ROOT, "%s§%d§%d", server.getMotd(), server.getPlayerCount(), server.getMaxPlayers());
|
||||
- }
|
||||
-
|
||||
- private static String createVersion1Response(ServerInfo server) {
|
||||
- return String.format(
|
||||
- Locale.ROOT,
|
||||
- "§1\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d",
|
||||
- 127,
|
||||
- server.getServerVersion(),
|
||||
- server.getMotd(),
|
||||
- server.getPlayerCount(),
|
||||
- server.getMaxPlayers()
|
||||
- );
|
||||
- }
|
||||
+ // Paper start
|
||||
+ private static String readLegacyString(ByteBuf buf) {
|
||||
+ int size = buf.readShort() * Character.BYTES;
|
||||
+ if (!buf.isReadable(size)) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ String result = buf.toString(buf.readerIndex(), size, java.nio.charset.StandardCharsets.UTF_16BE);
|
||||
+ buf.skipBytes(size); // toString doesn't increase readerIndex automatically
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ private String readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) {
|
||||
+ ByteBuf buf = this.buf;
|
||||
+
|
||||
+ if (buf == null) {
|
||||
+ this.buf = buf = ctx.alloc().buffer();
|
||||
+ buf.markReaderIndex();
|
||||
+ } else {
|
||||
+ buf.resetReaderIndex();
|
||||
+ }
|
||||
+
|
||||
+ buf.writeBytes(part);
|
||||
+
|
||||
+ if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ String string = readLegacyString(buf);
|
||||
+ if (string == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ if (!string.equals(LegacyProtocolUtils.CUSTOM_PAYLOAD_PACKET_PING_CHANNEL)) {
|
||||
+ removeHandler(ctx);
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ int protocolVersion = buf.readByte();
|
||||
+ String host = readLegacyString(buf);
|
||||
+ if (host == null) {
|
||||
+ removeHandler(ctx);
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ int port = buf.readInt();
|
||||
+ if (buf.isReadable()) {
|
||||
+ removeHandler(ctx);
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ buf.release();
|
||||
+ this.buf = null;
|
||||
+
|
||||
+ LOGGER.debug("Ping: (1.6) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? ctx.channel().remoteAddress(): "<ip address withheld>"); // Paper - Respect logIPs option
|
||||
+
|
||||
+ net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer();
|
||||
+ java.net.InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port);
|
||||
+ com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(
|
||||
+ server, (java.net.InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost);
|
||||
+ if (event == null) {
|
||||
+ ctx.close();
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(),
|
||||
+ com.destroystokyo.paper.network.PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers());
|
||||
+ return response;
|
||||
+ }
|
||||
+
|
||||
+ private void removeHandler(ChannelHandlerContext ctx) {
|
||||
+ ByteBuf buf = this.buf;
|
||||
+ this.buf = null;
|
||||
+
|
||||
+ buf.resetReaderIndex();
|
||||
+ ctx.pipeline().remove(this);
|
||||
+ ctx.fireChannelRead(buf);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
+ if (this.buf != null) {
|
||||
+ this.buf.release();
|
||||
+ this.buf = null;
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
private static void sendFlushAndClose(ChannelHandlerContext context, ByteBuf buffer) {
|
||||
context.pipeline().firstContext().writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE);
|
@@ -0,0 +1,26 @@
|
||||
--- a/net/minecraft/server/network/PlayerChunkSender.java
|
||||
+++ b/net/minecraft/server/network/PlayerChunkSender.java
|
||||
@@ -44,6 +_,11 @@
|
||||
public void dropChunk(ServerPlayer player, ChunkPos chunkPos) {
|
||||
if (!this.pendingChunks.remove(chunkPos.toLong()) && player.isAlive()) {
|
||||
player.connection.send(new ClientboundForgetLevelChunkPacket(chunkPos));
|
||||
+ // Paper start - PlayerChunkUnloadEvent
|
||||
+ if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) {
|
||||
+ new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(chunkPos.longKey), player.getBukkitEntity()).callEvent();
|
||||
+ }
|
||||
+ // Paper end - PlayerChunkUnloadEvent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +_,11 @@
|
||||
|
||||
private static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) {
|
||||
packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null));
|
||||
+ // Paper start - PlayerChunkLoadEvent
|
||||
+ if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) {
|
||||
+ new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), packetListener.getPlayer().getBukkitEntity()).callEvent();
|
||||
+ }
|
||||
+ // Paper end - PlayerChunkLoadEvent
|
||||
ChunkPos pos = chunk.getPos();
|
||||
DebugPackets.sendPoiPacketsForChunk(level, pos);
|
||||
}
|
@@ -0,0 +1,387 @@
|
||||
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
|
||||
@@ -27,30 +_,82 @@
|
||||
import net.minecraft.util.profiling.Profiler;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
-public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener {
|
||||
+// CraftBukkit start
|
||||
+import io.netty.buffer.ByteBuf;
|
||||
+import java.util.concurrent.ExecutionException;
|
||||
+import net.minecraft.network.ConnectionProtocol;
|
||||
+import net.minecraft.network.protocol.common.custom.DiscardedPayload;
|
||||
+import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
|
||||
+import net.minecraft.resources.ResourceLocation;
|
||||
+import net.minecraft.server.level.ServerPlayer;
|
||||
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
+import org.bukkit.craftbukkit.util.CraftLocation;
|
||||
+import org.bukkit.craftbukkit.util.Waitable;
|
||||
+import org.bukkit.event.player.PlayerKickEvent;
|
||||
+import org.bukkit.event.player.PlayerResourcePackStatusEvent;
|
||||
+
|
||||
+public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, CraftPlayer.TransferCookieConnection {
|
||||
+ // CraftBukkit end
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
public static final int LATENCY_CHECK_INTERVAL = 15000;
|
||||
private static final int CLOSED_LISTENER_TIMEOUT = 15000;
|
||||
private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout");
|
||||
static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response");
|
||||
protected final MinecraftServer server;
|
||||
- protected final Connection connection;
|
||||
+ public final Connection connection; // Paper
|
||||
private final boolean transferred;
|
||||
- private long keepAliveTime;
|
||||
+ private long keepAliveTime = Util.getMillis(); // Paper
|
||||
private boolean keepAlivePending;
|
||||
private long keepAliveChallenge;
|
||||
private long closedListenerTime;
|
||||
private boolean closed = false;
|
||||
private int latency;
|
||||
private volatile boolean suspendFlushingOnServerThread = false;
|
||||
+ // CraftBukkit start
|
||||
+ protected final ServerPlayer player;
|
||||
+ protected final org.bukkit.craftbukkit.CraftServer cserver;
|
||||
+ public boolean processedDisconnect;
|
||||
+ // CraftBukkit end
|
||||
+ public final java.util.Map<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
|
||||
+ private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit
|
||||
+ protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
|
||||
|
||||
- public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) {
|
||||
+ public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) { // CraftBukkit
|
||||
this.server = server;
|
||||
this.connection = connection;
|
||||
this.keepAliveTime = Util.getMillis();
|
||||
this.latency = cookie.latency();
|
||||
this.transferred = cookie.transferred();
|
||||
- }
|
||||
+ // CraftBukkit start - add fields and methods
|
||||
+ this.player = player;
|
||||
+ this.player.transferCookieConnection = this;
|
||||
+ this.cserver = server.server;
|
||||
+ }
|
||||
+
|
||||
+ public CraftPlayer getCraftPlayer() {
|
||||
+ return this.player == null ? null : this.player.getBukkitEntity();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isTransferred() {
|
||||
+ return this.transferred;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public ConnectionProtocol getProtocol() {
|
||||
+ return this.protocol();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void sendPacket(Packet<?> packet) {
|
||||
+ this.send(packet);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes
|
||||
+ this.disconnect(reason, cause); // Paper - kick event causes
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
|
||||
private void close() {
|
||||
if (!this.closed) {
|
||||
@@ -61,6 +_,12 @@
|
||||
|
||||
@Override
|
||||
public void onDisconnect(DisconnectionDetails details) {
|
||||
+ // Paper start - Fix kick event leave message not being sent
|
||||
+ this.onDisconnect(details, null);
|
||||
+ }
|
||||
+
|
||||
+ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) {
|
||||
+ // Paper end - Fix kick event leave message not being sent
|
||||
if (this.isSingleplayerOwner()) {
|
||||
LOGGER.info("Stopping singleplayer server as player logged out");
|
||||
this.server.halt(false);
|
||||
@@ -80,7 +_,7 @@
|
||||
this.latency = (this.latency * 3 + i) / 4;
|
||||
this.keepAlivePending = false;
|
||||
} else if (!this.isSingleplayerOwner()) {
|
||||
- this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
|
||||
+ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,37 +_,124 @@
|
||||
public void handlePong(ServerboundPongPacket packet) {
|
||||
}
|
||||
|
||||
+ private static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register"); // CraftBukkit
|
||||
+ private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister"); // CraftBukkit
|
||||
+
|
||||
@Override
|
||||
public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
|
||||
- }
|
||||
+ // CraftBukkit start
|
||||
+ // Paper start - Brand support
|
||||
+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload(String brand)) {
|
||||
+ this.player.clientBrandName = brand;
|
||||
+ }
|
||||
+ // Paper end - Brand support
|
||||
+ if (!(packet.payload() instanceof DiscardedPayload)) {
|
||||
+ return;
|
||||
+ }
|
||||
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
|
||||
+ ResourceLocation identifier = packet.payload().type().id();
|
||||
+ ByteBuf payload = ((DiscardedPayload)packet.payload()).data();
|
||||
+
|
||||
+ if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_REGISTER)) {
|
||||
+ try {
|
||||
+ String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
|
||||
+ for (String channel : channels.split("\0")) {
|
||||
+ this.getCraftPlayer().addChannel(channel);
|
||||
+ }
|
||||
+ } catch (Exception ex) {
|
||||
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex);
|
||||
+ this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
|
||||
+ }
|
||||
+ } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) {
|
||||
+ try {
|
||||
+ String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
|
||||
+ for (String channel : channels.split("\0")) {
|
||||
+ this.getCraftPlayer().removeChannel(channel);
|
||||
+ }
|
||||
+ } catch (Exception ex) {
|
||||
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn't unregister custom payload", ex);
|
||||
+ this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
|
||||
+ }
|
||||
+ } else {
|
||||
+ try {
|
||||
+ byte[] data = new byte[payload.readableBytes()];
|
||||
+ payload.readBytes(data);
|
||||
+ // Paper start - Brand support; Retain this incase upstream decides to 'break' the new mechanism in favour of backwards compat...
|
||||
+ if (identifier.equals(MINECRAFT_BRAND)) {
|
||||
+ try {
|
||||
+ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256);
|
||||
+ } catch (StringIndexOutOfBoundsException ex) {
|
||||
+ this.player.clientBrandName = "illegal";
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - Brand support
|
||||
+ this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data);
|
||||
+ } catch (Exception ex) {
|
||||
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn't dispatch custom payload", ex);
|
||||
+ this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public final boolean isDisconnected() {
|
||||
+ return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
|
||||
@Override
|
||||
public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {
|
||||
PacketUtils.ensureRunningOnSameThread(packet, this, this.server);
|
||||
if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) {
|
||||
LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id());
|
||||
- this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"));
|
||||
- }
|
||||
+ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause
|
||||
+ }
|
||||
+ // Paper start - adventure pack callbacks
|
||||
+ // call the callbacks before the previously-existing event so the event has final say
|
||||
+ final net.kyori.adventure.resource.ResourcePackCallback callback;
|
||||
+ if (packet.action().isTerminal()) {
|
||||
+ callback = this.packCallbacks.remove(packet.id());
|
||||
+ } else {
|
||||
+ callback = this.packCallbacks.get(packet.id());
|
||||
+ }
|
||||
+ if (callback != null) {
|
||||
+ callback.packEventReceived(packet.id(), net.kyori.adventure.resource.ResourcePackStatus.valueOf(packet.action().name()), this.getCraftPlayer());
|
||||
+ }
|
||||
+ // Paper end
|
||||
+ // Paper start - store last pack status
|
||||
+ PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()];
|
||||
+ this.player.getBukkitEntity().resourcePackStatus = packStatus;
|
||||
+ this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit
|
||||
+ // Paper end - store last pack status
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCookieResponse(ServerboundCookieResponsePacket packet) {
|
||||
- this.disconnect(DISCONNECT_UNEXPECTED_QUERY);
|
||||
+ // CraftBukkit start
|
||||
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.server);
|
||||
+ if (this.player.getBukkitEntity().handleCookieResponse(packet)) {
|
||||
+ return;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+ this.disconnect(DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause
|
||||
}
|
||||
|
||||
protected void keepConnectionAlive() {
|
||||
Profiler.get().push("keepAlive");
|
||||
- long millis = Util.getMillis();
|
||||
- if (!this.isSingleplayerOwner() && millis - this.keepAliveTime >= 15000L) {
|
||||
- if (this.keepAlivePending) {
|
||||
- this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
|
||||
- } else if (this.checkIfClosed(millis)) {
|
||||
+ // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings
|
||||
+ // This should effectively place the keepalive handling back to "as it was" before 1.12.2
|
||||
+ long currentTime = Util.getMillis();
|
||||
+ long elapsedTime = currentTime - this.keepAliveTime;
|
||||
+ if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets
|
||||
+ if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected
|
||||
+ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
|
||||
+ } else if (this.checkIfClosed(currentTime)) { // Paper
|
||||
this.keepAlivePending = true;
|
||||
- this.keepAliveTime = millis;
|
||||
- this.keepAliveChallenge = millis;
|
||||
+ this.keepAliveTime = currentTime;
|
||||
+ this.keepAliveChallenge = currentTime;
|
||||
this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge));
|
||||
}
|
||||
}
|
||||
+ // Paper end - give clients a longer time to respond to pings as per pre 1.12.2 timings
|
||||
|
||||
Profiler.get().pop();
|
||||
}
|
||||
@@ -126,7 +_,7 @@
|
||||
private boolean checkIfClosed(long time) {
|
||||
if (this.closed) {
|
||||
if (time - this.closedListenerTime >= 15000L) {
|
||||
- this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
|
||||
+ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -149,6 +_,13 @@
|
||||
}
|
||||
|
||||
public void send(Packet<?> packet, @Nullable PacketSendListener listener) {
|
||||
+ // CraftBukkit start
|
||||
+ if (packet == null || this.processedDisconnect) { // Spigot
|
||||
+ return;
|
||||
+ } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket) {
|
||||
+ this.player.compassTarget = CraftLocation.toBukkit(defaultSpawnPositionPacket.getPos(), this.getCraftPlayer().getWorld());
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
if (packet.isTerminal()) {
|
||||
this.close();
|
||||
}
|
||||
@@ -165,19 +_,108 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ // Paper start - adventure
|
||||
+ public void disconnect(final net.kyori.adventure.text.Component reason) {
|
||||
+ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
|
||||
+ }
|
||||
+
|
||||
+ public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
|
||||
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
|
||||
+ }
|
||||
+
|
||||
+ // Paper end - adventure
|
||||
+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes
|
||||
public void disconnect(Component reason) {
|
||||
- this.disconnect(new DisconnectionDetails(reason));
|
||||
- }
|
||||
-
|
||||
- public void disconnect(DisconnectionDetails disconnectionDetails) {
|
||||
+ // Paper start - kick event causes
|
||||
+ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
|
||||
+ }
|
||||
+
|
||||
+ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) {
|
||||
+ this.disconnect(new DisconnectionDetails(reason), cause);
|
||||
+ // Paper end - kick event causes
|
||||
+ }
|
||||
+
|
||||
+ public void disconnect(DisconnectionDetails disconnectionDetails, PlayerKickEvent.Cause cause) { // Paper - kick event cause
|
||||
+ // CraftBukkit start - fire PlayerKickEvent
|
||||
+ if (this.processedDisconnect) {
|
||||
+ return;
|
||||
+ }
|
||||
+ if (!this.cserver.isPrimaryThread()) {
|
||||
+ Waitable waitable = new Waitable() {
|
||||
+ @Override
|
||||
+ protected Object evaluate() {
|
||||
+ ServerCommonPacketListenerImpl.this.disconnect(disconnectionDetails, cause); // Paper - kick event causes
|
||||
+ return null;
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ this.server.processQueue.add(waitable);
|
||||
+
|
||||
+ try {
|
||||
+ waitable.get();
|
||||
+ } catch (InterruptedException e) {
|
||||
+ Thread.currentThread().interrupt();
|
||||
+ } catch (ExecutionException e) {
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure
|
||||
+
|
||||
+ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()), leaveMessage, cause); // Paper - adventure & kick event causes
|
||||
+
|
||||
+ if (this.cserver.getServer().isRunning()) {
|
||||
+ this.cserver.getPluginManager().callEvent(event);
|
||||
+ }
|
||||
+
|
||||
+ if (event.isCancelled()) {
|
||||
+ // Do not kick the player
|
||||
+ return;
|
||||
+ }
|
||||
+ // Send the possibly modified leave message
|
||||
+ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionDetails.report(), disconnectionDetails.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message
|
||||
+ }
|
||||
+
|
||||
+ private void disconnect0(DisconnectionDetails disconnectionDetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message
|
||||
+ // CraftBukkit end
|
||||
+ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason
|
||||
this.connection
|
||||
.send(
|
||||
new ClientboundDisconnectPacket(disconnectionDetails.reason()),
|
||||
PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails))
|
||||
);
|
||||
- this.connection.setReadOnly();
|
||||
- this.server.executeBlocking(this.connection::handleDisconnection);
|
||||
- }
|
||||
+ this.onDisconnect(disconnectionDetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message
|
||||
+ this.connection.setReadOnly();
|
||||
+ // CraftBukkit - Don't wait
|
||||
+ this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper
|
||||
+ }
|
||||
+
|
||||
+ // Paper start - add proper async disconnect
|
||||
+ public void disconnectAsync(net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
|
||||
+ this.disconnectAsync(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
|
||||
+ }
|
||||
+
|
||||
+ public void disconnectAsync(Component reason, PlayerKickEvent.Cause cause) {
|
||||
+ this.disconnectAsync(new DisconnectionDetails(reason), cause);
|
||||
+ }
|
||||
+
|
||||
+ public void disconnectAsync(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) {
|
||||
+ if (this.cserver.isPrimaryThread()) {
|
||||
+ this.disconnect(disconnectionInfo, cause);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ this.connection.setReadOnly();
|
||||
+ this.server.scheduleOnMain(() -> {
|
||||
+ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause);
|
||||
+ if (ServerCommonPacketListenerImpl.this.player.quitReason == null) {
|
||||
+ // cancelled
|
||||
+ ServerCommonPacketListenerImpl.this.connection.enableAutoRead();
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+ // Paper end - add proper async disconnect
|
||||
|
||||
protected boolean isSingleplayerOwner() {
|
||||
return this.server.isSingleplayerOwner(this.playerProfile());
|
@@ -0,0 +1,71 @@
|
||||
--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
|
||||
@@ -48,8 +_,10 @@
|
||||
@Nullable
|
||||
private SynchronizeRegistriesTask synchronizeRegistriesTask;
|
||||
|
||||
- public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) {
|
||||
- super(server, connection, cookie);
|
||||
+ // CraftBukkit start
|
||||
+ public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) {
|
||||
+ super(server, connection, cookie, player);
|
||||
+ // CraftBukkit end
|
||||
this.gameProfile = cookie.gameProfile();
|
||||
this.clientInformation = cookie.clientInformation();
|
||||
}
|
||||
@@ -61,6 +_,10 @@
|
||||
|
||||
@Override
|
||||
public void onDisconnect(DisconnectionDetails details) {
|
||||
+ // Paper start - Debugging
|
||||
+ if (this.server.isDebugging()) {
|
||||
+ ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}, while in configuration phase {}", this.gameProfile, details.reason().getString(), this.currentTask != null ? this.currentTask.type().id() : "null");
|
||||
+ } else // Paper end
|
||||
LOGGER.info("{} lost connection: {}", this.gameProfile, details.reason().getString());
|
||||
super.onDisconnect(details);
|
||||
}
|
||||
@@ -73,6 +_,12 @@
|
||||
public void startConfiguration() {
|
||||
this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName())));
|
||||
ServerLinks serverLinks = this.server.serverLinks();
|
||||
+ // CraftBukkit start
|
||||
+ org.bukkit.craftbukkit.CraftServerLinks wrapper = new org.bukkit.craftbukkit.CraftServerLinks(serverLinks);
|
||||
+ org.bukkit.event.player.PlayerLinksSendEvent event = new org.bukkit.event.player.PlayerLinksSendEvent(this.player.getBukkitEntity(), wrapper);
|
||||
+ this.cserver.getPluginManager().callEvent(event);
|
||||
+ serverLinks = wrapper.getServerLinks();
|
||||
+ // CraftBukkit end
|
||||
if (!serverLinks.isEmpty()) {
|
||||
this.send(new ClientboundServerLinksPacket(serverLinks.untrust()));
|
||||
}
|
||||
@@ -105,6 +_,7 @@
|
||||
@Override
|
||||
public void handleClientInformation(ServerboundClientInformationPacket packet) {
|
||||
this.clientInformation = packet.information();
|
||||
+ this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,16 +_,21 @@
|
||||
return;
|
||||
}
|
||||
|
||||
- Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile);
|
||||
+ Component component = null; // CraftBukkit - login checks already completed
|
||||
if (component != null) {
|
||||
this.disconnect(component);
|
||||
return;
|
||||
}
|
||||
|
||||
- ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation);
|
||||
+ ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
|
||||
playerList.placeNewPlayer(this.connection, playerForLogin, this.createCookie(this.clientInformation));
|
||||
} catch (Exception var5) {
|
||||
LOGGER.error("Couldn't place player in world", (Throwable)var5);
|
||||
+ // Paper start - Debugging
|
||||
+ if (this.server.isDebugging()) {
|
||||
+ var5.printStackTrace();
|
||||
+ }
|
||||
+ // Paper end - Debugging
|
||||
this.connection.send(new ClientboundDisconnectPacket(DISCONNECT_REASON_INVALID_DATA));
|
||||
this.connection.disconnect(DISCONNECT_REASON_INVALID_DATA);
|
||||
}
|
@@ -0,0 +1,154 @@
|
||||
--- a/net/minecraft/server/network/ServerConnectionListener.java
|
||||
+++ b/net/minecraft/server/network/ServerConnectionListener.java
|
||||
@@ -49,10 +_,10 @@
|
||||
public class ServerConnectionListener {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
public static final Supplier<NioEventLoopGroup> SERVER_EVENT_GROUP = Suppliers.memoize(
|
||||
- () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Server IO #%d").setDaemon(true).build())
|
||||
+ () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper
|
||||
);
|
||||
public static final Supplier<EpollEventLoopGroup> SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(
|
||||
- () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build())
|
||||
+ () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper
|
||||
);
|
||||
final MinecraftServer server;
|
||||
public volatile boolean running;
|
||||
@@ -64,12 +_,35 @@
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
+ // Paper start - prevent blocking on adding a new connection while the server is ticking
|
||||
+ private final java.util.Queue<Connection> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
||||
+
|
||||
+ private final void addPending() {
|
||||
+ Connection connection;
|
||||
+ while ((connection = this.pending.poll()) != null) {
|
||||
+ this.connections.add(connection);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - prevent blocking on adding a new connection while the server is ticking
|
||||
+
|
||||
public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException {
|
||||
+ // Paper start - Unix domain socket support
|
||||
+ this.startTcpServerListener(new java.net.InetSocketAddress(address, port));
|
||||
+ }
|
||||
+
|
||||
+ public void startTcpServerListener(SocketAddress address) throws IOException {
|
||||
+ // Paper end - Unix domain socket support
|
||||
synchronized (this.channels) {
|
||||
- Class<? extends ServerSocketChannel> clazz;
|
||||
+ Class<? extends io.netty.channel.ServerChannel> clazz; // Paper - Unix domain socket support
|
||||
EventLoopGroup eventLoopGroup;
|
||||
if (Epoll.isAvailable() && this.server.isEpollEnabled()) {
|
||||
+ // Paper start - Unix domain socket support
|
||||
+ if (address instanceof io.netty.channel.unix.DomainSocketAddress) {
|
||||
+ clazz = io.netty.channel.epoll.EpollServerDomainSocketChannel.class;
|
||||
+ } else {
|
||||
clazz = EpollServerSocketChannel.class;
|
||||
+ }
|
||||
+ // Paper end - Unix domain socket support
|
||||
eventLoopGroup = SERVER_EPOLL_EVENT_GROUP.get();
|
||||
LOGGER.info("Using epoll channel type");
|
||||
} else {
|
||||
@@ -78,6 +_,12 @@
|
||||
LOGGER.info("Using default channel type");
|
||||
}
|
||||
|
||||
+ // Paper start - Warn people with console access that HAProxy is in use.
|
||||
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
|
||||
+ LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled.");
|
||||
+ }
|
||||
+ // Paper end - Warn people with console access that HAProxy is in use.
|
||||
+
|
||||
this.channels
|
||||
.add(
|
||||
new ServerBootstrap()
|
||||
@@ -101,22 +_,64 @@
|
||||
Connection connection = (Connection)(rateLimitPacketsPerSecond > 0
|
||||
? new RateKickingConnection(rateLimitPacketsPerSecond)
|
||||
: new Connection(PacketFlow.SERVERBOUND));
|
||||
- ServerConnectionListener.this.connections.add(connection);
|
||||
+ // ServerConnectionListener.this.connections.add(connection);
|
||||
+ // Paper start - Add support for Proxy Protocol
|
||||
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
|
||||
+ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder());
|
||||
+ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() {
|
||||
+ @Override
|
||||
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
+ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) {
|
||||
+ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) {
|
||||
+ String realaddress = message.sourceAddress();
|
||||
+ int realport = message.sourcePort();
|
||||
+
|
||||
+ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport);
|
||||
+
|
||||
+ Connection connection = (Connection) channel.pipeline().get("packet_handler");
|
||||
+ connection.address = socketaddr;
|
||||
+ // Paper start - Add API to get player's proxy address
|
||||
+ final String proxyAddress = message.destinationAddress();
|
||||
+ final int proxyPort = message.destinationPort();
|
||||
+
|
||||
+ connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort);
|
||||
+ // Paper end - Add API to get player's proxy address
|
||||
+ }
|
||||
+ } else {
|
||||
+ super.channelRead(ctx, msg);
|
||||
+ }
|
||||
+ }
|
||||
+ });
|
||||
+ }
|
||||
+ // Paper end - Add support for proxy protocol
|
||||
+ pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking
|
||||
connection.configurePacketHandler(channelPipeline);
|
||||
connection.setListenerForServerboundHandshake(
|
||||
new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection)
|
||||
);
|
||||
+ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners
|
||||
}
|
||||
}
|
||||
)
|
||||
.group(eventLoopGroup)
|
||||
- .localAddress(address, port)
|
||||
+ .localAddress(address) // Paper - Unix domain socket support
|
||||
+ .option(ChannelOption.AUTO_READ, false) // CraftBukkit
|
||||
.bind()
|
||||
.syncUninterruptibly()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+ // CraftBukkit start
|
||||
+ public void acceptConnections() {
|
||||
+ synchronized (this.channels) {
|
||||
+ for (ChannelFuture future : this.channels) {
|
||||
+ future.channel().config().setAutoRead(true);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
public SocketAddress startMemoryChannel() {
|
||||
ChannelFuture channelFuture;
|
||||
synchronized (this.channels) {
|
||||
@@ -161,6 +_,13 @@
|
||||
|
||||
public void tick() {
|
||||
synchronized (this.connections) {
|
||||
+ // Spigot start
|
||||
+ this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking
|
||||
+ // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order
|
||||
+ if (org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0) {
|
||||
+ Collections.shuffle(this.connections);
|
||||
+ }
|
||||
+ // Spigot end
|
||||
Iterator<Connection> iterator = this.connections.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
@@ -180,6 +_,7 @@
|
||||
connection.setReadOnly();
|
||||
}
|
||||
} else {
|
||||
+ if (connection.preparing) continue; // Spigot - Fix a race condition where a NetworkManager could be unregistered just before connection
|
||||
iterator.remove();
|
||||
connection.handleDisconnection();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
|
||||
@@ -12,11 +_,27 @@
|
||||
import net.minecraft.network.protocol.status.StatusProtocols;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
+// CraftBukkit start
|
||||
+import java.net.InetAddress;
|
||||
+import java.util.HashMap;
|
||||
+// CraftBukkit end
|
||||
+
|
||||
public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketListener {
|
||||
private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request");
|
||||
+ // Spigot start
|
||||
+ private static final com.google.gson.Gson gson = new com.google.gson.Gson();
|
||||
+ static final java.util.regex.Pattern HOST_PATTERN = java.util.regex.Pattern.compile("[0-9a-f\\.:]{0,45}");
|
||||
+ static final java.util.regex.Pattern PROP_PATTERN = java.util.regex.Pattern.compile("\\w{0,16}");
|
||||
+ // Spigot end
|
||||
+ // CraftBukkit start - add fields
|
||||
+ private static final HashMap<InetAddress, Long> throttleTracker = new HashMap<>();
|
||||
+ private static int throttleCounter = 0;
|
||||
+ // CraftBukkit end
|
||||
+ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper
|
||||
private final MinecraftServer server;
|
||||
private final Connection connection;
|
||||
|
||||
+
|
||||
public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) {
|
||||
this.server = server;
|
||||
this.connection = connection;
|
||||
@@ -24,6 +_,7 @@
|
||||
|
||||
@Override
|
||||
public void handleIntention(ClientIntentionPacket packet) {
|
||||
+ this.connection.hostname = packet.hostName() + ":" + packet.port(); // CraftBukkit - set hostname
|
||||
switch (packet.intention()) {
|
||||
case LOGIN:
|
||||
this.beginLogin(packet, false);
|
||||
@@ -50,22 +_,117 @@
|
||||
default:
|
||||
throw new UnsupportedOperationException("Invalid intention " + packet.intention());
|
||||
}
|
||||
+
|
||||
+ // Paper start - NetworkClient implementation
|
||||
+ this.connection.protocolVersion = packet.protocolVersion();
|
||||
+ this.connection.virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(packet.hostName(), packet.port());
|
||||
+ // Paper end
|
||||
}
|
||||
|
||||
private void beginLogin(ClientIntentionPacket packet, boolean transferred) {
|
||||
this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND);
|
||||
+ // CraftBukkit start - Connection throttle
|
||||
+ try {
|
||||
+ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket
|
||||
+ long currentTime = System.currentTimeMillis();
|
||||
+ long connectionThrottle = this.server.server.getConnectionThrottle();
|
||||
+ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
|
||||
+
|
||||
+ synchronized (ServerHandshakePacketListenerImpl.throttleTracker) {
|
||||
+ if (ServerHandshakePacketListenerImpl.throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - ServerHandshakePacketListenerImpl.throttleTracker.get(address) < connectionThrottle) {
|
||||
+ ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime);
|
||||
+ Component chatmessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.connectionThrottle); // Paper - Configurable connection throttle kick message
|
||||
+ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
|
||||
+ this.connection.disconnect(chatmessage);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime);
|
||||
+ ServerHandshakePacketListenerImpl.throttleCounter++;
|
||||
+ if (ServerHandshakePacketListenerImpl.throttleCounter > 200) {
|
||||
+ ServerHandshakePacketListenerImpl.throttleCounter = 0;
|
||||
+
|
||||
+ // Cleanup stale entries
|
||||
+ ServerHandshakePacketListenerImpl.throttleTracker.values().removeIf(time -> time > connectionThrottle);
|
||||
+ }
|
||||
+ }
|
||||
+ } // Paper - Unix domain socket support
|
||||
+ } catch (Throwable t) {
|
||||
+ org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) {
|
||||
- Component component;
|
||||
- if (packet.protocolVersion() < 754) {
|
||||
- component = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName());
|
||||
+ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages
|
||||
+ if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message
|
||||
+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages
|
||||
} else {
|
||||
- component = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName());
|
||||
+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages
|
||||
}
|
||||
+ Component component = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages
|
||||
|
||||
this.connection.send(new ClientboundLoginDisconnectPacket(component));
|
||||
this.connection.disconnect(component);
|
||||
} else {
|
||||
this.connection.setupInboundProtocol(LoginProtocols.SERVERBOUND, new ServerLoginPacketListenerImpl(this.server, this.connection, transferred));
|
||||
+ // Paper start - PlayerHandshakeEvent
|
||||
+ boolean proxyLogicEnabled = org.spigotmc.SpigotConfig.bungee;
|
||||
+ boolean handledByEvent = false;
|
||||
+ // Try and handle the handshake through the event
|
||||
+ if (com.destroystokyo.paper.event.player.PlayerHandshakeEvent.getHandlerList().getRegisteredListeners().length != 0) { // Hello? Can you hear me?
|
||||
+ java.net.SocketAddress socketAddress = this.connection.address;
|
||||
+ String hostnameOfRemote = socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getHostString() : InetAddress.getLoopbackAddress().getHostAddress();
|
||||
+ com.destroystokyo.paper.event.player.PlayerHandshakeEvent event = new com.destroystokyo.paper.event.player.PlayerHandshakeEvent(packet.hostName(), hostnameOfRemote, !proxyLogicEnabled);
|
||||
+ if (event.callEvent()) {
|
||||
+ // If we've failed somehow, let the client know so and go no further.
|
||||
+ if (event.isFailed()) {
|
||||
+ Component component = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.failMessage());
|
||||
+ this.connection.send(new ClientboundLoginDisconnectPacket(component));
|
||||
+ this.connection.disconnect(component);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (event.getServerHostname() != null) {
|
||||
+ // change hostname
|
||||
+ packet = new ClientIntentionPacket(
|
||||
+ packet.protocolVersion(),
|
||||
+ event.getServerHostname(),
|
||||
+ packet.port(),
|
||||
+ packet.intention()
|
||||
+ );
|
||||
+ }
|
||||
+ if (event.getSocketAddressHostname() != null) this.connection.address = new java.net.InetSocketAddress(event.getSocketAddressHostname(), socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0);
|
||||
+ this.connection.spoofedUUID = event.getUniqueId();
|
||||
+ this.connection.spoofedProfile = gson.fromJson(event.getPropertiesJson(), com.mojang.authlib.properties.Property[].class);
|
||||
+ handledByEvent = true; // Hooray, we did it!
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
+ // Spigot Start
|
||||
+ String[] split = packet.hostName().split("\00");
|
||||
+ if (!handledByEvent && proxyLogicEnabled) { // Paper
|
||||
+ // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above!
|
||||
+ if ((split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) { // Paper - Add bypass host check
|
||||
+ // Paper start - Unix domain socket support
|
||||
+ java.net.SocketAddress socketAddress = this.connection.getRemoteAddress();
|
||||
+ this.connection.hostname = split[0];
|
||||
+ this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0);
|
||||
+ // Paper end - Unix domain socket support
|
||||
+ this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] );
|
||||
+ } else {
|
||||
+ Component chatmessage = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!");
|
||||
+ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
|
||||
+ this.connection.disconnect(chatmessage);
|
||||
+ return;
|
||||
+ }
|
||||
+ if (split.length == 4) {
|
||||
+ this.connection.spoofedProfile = ServerHandshakePacketListenerImpl.gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class);
|
||||
+ }
|
||||
+ } else if ((split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) {
|
||||
+ Component chatmessage = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?");
|
||||
+ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
|
||||
+ this.connection.disconnect(chatmessage);
|
||||
+ }
|
||||
+ // Spigot End
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,405 @@
|
||||
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
|
||||
@@ -43,10 +_,19 @@
|
||||
import net.minecraft.util.StringUtil;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
+// CraftBukkit start
|
||||
+import net.minecraft.network.protocol.Packet;
|
||||
+import net.minecraft.network.protocol.PacketUtils;
|
||||
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
+import org.bukkit.craftbukkit.util.Waitable;
|
||||
+import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
+import org.bukkit.event.player.PlayerPreLoginEvent;
|
||||
|
||||
-public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener {
|
||||
+public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener, CraftPlayer.TransferCookieConnection {
|
||||
+ // CraftBukkit end
|
||||
private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
|
||||
static final Logger LOGGER = LogUtils.getLogger();
|
||||
+ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads
|
||||
private static final int MAX_TICKS_BEFORE_LOGIN = 600;
|
||||
private final byte[] challenge;
|
||||
final MinecraftServer server;
|
||||
@@ -56,9 +_,12 @@
|
||||
@Nullable
|
||||
String requestedUsername;
|
||||
@Nullable
|
||||
- private GameProfile authenticatedProfile;
|
||||
+ public GameProfile authenticatedProfile; // Paper - public
|
||||
private final String serverId = "";
|
||||
private final boolean transferred;
|
||||
+ private net.minecraft.server.level.ServerPlayer player; // CraftBukkit
|
||||
+ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding
|
||||
+ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support
|
||||
|
||||
public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) {
|
||||
this.server = server;
|
||||
@@ -67,11 +_,48 @@
|
||||
this.transferred = transferred;
|
||||
}
|
||||
|
||||
+ // CraftBukkit start
|
||||
+ @Override
|
||||
+ public boolean isTransferred() {
|
||||
+ return this.transferred;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public net.minecraft.network.ConnectionProtocol getProtocol() {
|
||||
+ return net.minecraft.network.ConnectionProtocol.LOGIN;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void sendPacket(Packet<?> packet) {
|
||||
+ this.connection.send(packet);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes - during login, no event can be called.
|
||||
+ this.disconnect(reason);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
@Override
|
||||
public void tick() {
|
||||
+ // Paper start - Do not allow logins while the server is shutting down
|
||||
+ if (!this.server.isRunning()) {
|
||||
+ this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.spigotmc.SpigotConfig.restartMessage)[0]);
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - Do not allow logins while the server is shutting down
|
||||
+
|
||||
if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) {
|
||||
+ if (this.connection.isConnected()) { // Paper - prevent logins to be processed even though disconnect was called
|
||||
this.verifyLoginAndFinishConnectionSetup(Objects.requireNonNull(this.authenticatedProfile));
|
||||
- }
|
||||
+ } // Paper - prevent logins to be processed even though disconnect was called
|
||||
+ }
|
||||
+
|
||||
+ // CraftBukkit start
|
||||
+ if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES && !this.player.getBukkitEntity().isAwaitingCookies()) {
|
||||
+ this.postCookies(this.authenticatedProfile);
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
|
||||
if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT
|
||||
&& !this.isPlayerAlreadyInWorld(Objects.requireNonNull(this.authenticatedProfile))) {
|
||||
@@ -83,6 +_,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ // CraftBukkit start
|
||||
+ @Deprecated
|
||||
+ public void disconnect(String reason) {
|
||||
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(reason))); // Paper - Fix hex colors not working in some kick messages
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
+
|
||||
@Override
|
||||
public boolean isAcceptingMessages() {
|
||||
return this.connection.isConnected();
|
||||
@@ -115,7 +_,13 @@
|
||||
@Override
|
||||
public void handleHello(ServerboundHelloPacket packet) {
|
||||
Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet");
|
||||
- Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username");
|
||||
+ // Paper start - Validate usernames
|
||||
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
|
||||
+ && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation
|
||||
+ && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) {
|
||||
+ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username");
|
||||
+ }
|
||||
+ // Paper end - Validate usernames
|
||||
this.requestedUsername = packet.name();
|
||||
GameProfile singleplayerProfile = this.server.getSingleplayerProfile();
|
||||
if (singleplayerProfile != null && this.requestedUsername.equalsIgnoreCase(singleplayerProfile.getName())) {
|
||||
@@ -125,7 +_,32 @@
|
||||
this.state = ServerLoginPacketListenerImpl.State.KEY;
|
||||
this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge, true));
|
||||
} else {
|
||||
- this.startClientVerification(UUIDUtil.createOfflineProfile(this.requestedUsername));
|
||||
+ // Paper start - Add Velocity IP Forwarding Support
|
||||
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) {
|
||||
+ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt();
|
||||
+ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer());
|
||||
+ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION);
|
||||
+ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket.PlayerInfoChannelPayload(com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf));
|
||||
+ this.connection.send(packet1);
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - Add Velocity IP Forwarding Support
|
||||
+ // CraftBukkit start
|
||||
+ // Paper start - Cache authenticator threads
|
||||
+ authenticatorPool.execute(() -> {
|
||||
+ try {
|
||||
+ GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot
|
||||
+
|
||||
+ gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
|
||||
+ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
|
||||
+ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
|
||||
+ } catch (Exception ex) {
|
||||
+ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!");
|
||||
+ ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + ServerLoginPacketListenerImpl.this.requestedUsername, ex);
|
||||
+ }
|
||||
+ });
|
||||
+ // Paper end - Cache authenticator threads
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,9 +_,23 @@
|
||||
|
||||
private void verifyLoginAndFinishConnectionSetup(GameProfile profile) {
|
||||
PlayerList playerList = this.server.getPlayerList();
|
||||
- Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), profile);
|
||||
- if (component != null) {
|
||||
- this.disconnect(component);
|
||||
+ // CraftBukkit start - fire PlayerLoginEvent
|
||||
+ this.player = playerList.canPlayerLogin(this, profile); // CraftBukkit
|
||||
+ if (this.player != null) {
|
||||
+ if (this.player.getBukkitEntity().isAwaitingCookies()) {
|
||||
+ this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES;
|
||||
+ } else {
|
||||
+ this.postCookies(profile);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void postCookies(GameProfile profile) {
|
||||
+ PlayerList playerList = this.server.getPlayerList();
|
||||
+
|
||||
+ if (this.player == null) {
|
||||
+ // this.disconnect(component);
|
||||
+ // CraftBukkit end
|
||||
} else {
|
||||
if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) {
|
||||
this.connection
|
||||
@@ -149,7 +_,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
- boolean flag = playerList.disconnectAllPlayersWithProfile(profile);
|
||||
+ boolean flag = playerList.disconnectAllPlayersWithProfile(profile, this.player); // CraftBukkit - add player reference
|
||||
if (flag) {
|
||||
this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT;
|
||||
} else {
|
||||
@@ -184,7 +_,8 @@
|
||||
throw new IllegalStateException("Protocol error", var7);
|
||||
}
|
||||
|
||||
- Thread thread = new Thread("User Authenticator #" + UNIQUE_THREAD_ID.incrementAndGet()) {
|
||||
+ // Paper start - Cache authenticator threads
|
||||
+ authenticatorPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized");
|
||||
@@ -195,11 +_,17 @@
|
||||
.hasJoinedServer(string1, string, this.getAddress());
|
||||
if (profileResult != null) {
|
||||
GameProfile gameProfile = profileResult.profile();
|
||||
+ // CraftBukkit start - fire PlayerPreLoginEvent
|
||||
+ if (!ServerLoginPacketListenerImpl.this.connection.isConnected()) {
|
||||
+ return;
|
||||
+ }
|
||||
+ gameProfile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameProfile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
|
||||
+ // CraftBukkit end
|
||||
ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameProfile.getName(), gameProfile.getId());
|
||||
ServerLoginPacketListenerImpl.this.startClientVerification(gameProfile);
|
||||
} else if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) {
|
||||
ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!");
|
||||
- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1));
|
||||
+ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot
|
||||
} else {
|
||||
ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
|
||||
ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", string1);
|
||||
@@ -207,11 +_,16 @@
|
||||
} catch (AuthenticationUnavailableException var4) {
|
||||
if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) {
|
||||
ServerLoginPacketListenerImpl.LOGGER.warn("Authentication servers are down but will let them in anyway!");
|
||||
- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1));
|
||||
+ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot
|
||||
} else {
|
||||
ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
|
||||
ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable");
|
||||
}
|
||||
+ // CraftBukkit start - catch all exceptions
|
||||
+ } catch (Exception ex) {
|
||||
+ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!");
|
||||
+ ServerLoginPacketListenerImpl.LOGGER.warn("Exception verifying {}", string1, ex);
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,24 +_,120 @@
|
||||
? ((InetSocketAddress)remoteAddress).getAddress()
|
||||
: null;
|
||||
}
|
||||
- };
|
||||
- thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
|
||||
- thread.start();
|
||||
- }
|
||||
+ });
|
||||
+ // Paper end - Cache authenticator threads
|
||||
+ }
|
||||
+
|
||||
+ // CraftBukkit start
|
||||
+ private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent
|
||||
+ // Paper start - Add Velocity IP Forwarding Support
|
||||
+ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) {
|
||||
+ this.disconnect("This server requires you to connect with Velocity.");
|
||||
+ return gameprofile;
|
||||
+ }
|
||||
+ // Paper end - Add Velocity IP Forwarding Support
|
||||
+ String playerName = gameprofile.getName();
|
||||
+ java.net.InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
|
||||
+ java.util.UUID uniqueId = gameprofile.getId();
|
||||
+ final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server;
|
||||
+
|
||||
+ // Paper start - Add more fields to AsyncPlayerPreLoginEvent
|
||||
+ final InetAddress rawAddress = ((InetSocketAddress) this.connection.channel.remoteAddress()).getAddress();
|
||||
+ com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile); // Paper - setPlayerProfileAPI
|
||||
+ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, this.transferred, profile, this.connection.hostname);
|
||||
+ server.getPluginManager().callEvent(asyncEvent);
|
||||
+ profile = asyncEvent.getPlayerProfile();
|
||||
+ profile.complete(true); // Paper - setPlayerProfileAPI
|
||||
+ gameprofile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile);
|
||||
+ playerName = gameprofile.getName();
|
||||
+ uniqueId = gameprofile.getId();
|
||||
+ // Paper end - Add more fields to AsyncPlayerPreLoginEvent
|
||||
+
|
||||
+ if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) {
|
||||
+ final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId);
|
||||
+ if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) {
|
||||
+ event.disallow(asyncEvent.getResult(), asyncEvent.kickMessage()); // Paper - Adventure
|
||||
+ }
|
||||
+ Waitable<PlayerPreLoginEvent.Result> waitable = new Waitable<>() {
|
||||
+ @Override
|
||||
+ protected PlayerPreLoginEvent.Result evaluate() {
|
||||
+ server.getPluginManager().callEvent(event);
|
||||
+ return event.getResult();
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ ServerLoginPacketListenerImpl.this.server.processQueue.add(waitable);
|
||||
+ if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) {
|
||||
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure
|
||||
+ }
|
||||
+ } else {
|
||||
+ if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
|
||||
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(asyncEvent.kickMessage())); // Paper - Adventure
|
||||
+ }
|
||||
+ }
|
||||
+ return gameprofile; // Paper - Add more fields to AsyncPlayerPreLoginEvent
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
|
||||
@Override
|
||||
public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) {
|
||||
- this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
|
||||
+ // Paper start - Add Velocity IP Forwarding Support
|
||||
+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) {
|
||||
+ ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload();
|
||||
+ if (payload == null) {
|
||||
+ this.disconnect("This server requires you to connect with Velocity.");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ net.minecraft.network.FriendlyByteBuf buf = payload.buffer;
|
||||
+ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) {
|
||||
+ this.disconnect("Unable to verify player details");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ int version = buf.readVarInt();
|
||||
+ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) {
|
||||
+ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION);
|
||||
+ }
|
||||
+
|
||||
+ java.net.SocketAddress listening = this.connection.getRemoteAddress();
|
||||
+ int port = 0;
|
||||
+ if (listening instanceof java.net.InetSocketAddress) {
|
||||
+ port = ((java.net.InetSocketAddress) listening).getPort();
|
||||
+ }
|
||||
+ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port);
|
||||
+
|
||||
+ this.authenticatedProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf);
|
||||
+
|
||||
+ //TODO Update handling for lazy sessions, might not even have to do anything?
|
||||
+
|
||||
+ // Proceed with login
|
||||
+ authenticatorPool.execute(() -> {
|
||||
+ try {
|
||||
+ final GameProfile gameprofile = this.callPlayerPreLoginEvents(this.authenticatedProfile);
|
||||
+ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
|
||||
+ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
|
||||
+ } catch (Exception ex) {
|
||||
+ disconnect("Failed to verify username!");
|
||||
+ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + this.authenticatedProfile.getName(), ex);
|
||||
+ }
|
||||
+ });
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end - Add Velocity IP Forwarding Support
|
||||
+ // this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket packet) {
|
||||
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); // CraftBukkit
|
||||
Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet");
|
||||
this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND);
|
||||
CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred);
|
||||
ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl(
|
||||
- this.server, this.connection, commonListenerCookie
|
||||
+ this.server, this.connection, commonListenerCookie, this.player // CraftBukkit
|
||||
);
|
||||
+
|
||||
this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverConfigurationPacketListenerImpl);
|
||||
serverConfigurationPacketListenerImpl.startConfiguration();
|
||||
this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;
|
||||
@@ -252,8 +_,36 @@
|
||||
|
||||
@Override
|
||||
public void handleCookieResponse(ServerboundCookieResponsePacket packet) {
|
||||
+ // CraftBukkit start
|
||||
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.server);
|
||||
+ if (this.player != null && this.player.getBukkitEntity().handleCookieResponse(packet)) {
|
||||
+ return;
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
|
||||
}
|
||||
+
|
||||
+ // Spigot start
|
||||
+ protected GameProfile createOfflineProfile(String s) {
|
||||
+ java.util.UUID uuid;
|
||||
+ if (this.connection.spoofedUUID != null) {
|
||||
+ uuid = this.connection.spoofedUUID;
|
||||
+ } else {
|
||||
+ uuid = UUIDUtil.createOfflinePlayerUUID(s);
|
||||
+ }
|
||||
+
|
||||
+ GameProfile gameProfile = new GameProfile(uuid, s);
|
||||
+
|
||||
+ if (this.connection.spoofedProfile != null) {
|
||||
+ for (com.mojang.authlib.properties.Property property : this.connection.spoofedProfile) {
|
||||
+ if (!ServerHandshakePacketListenerImpl.PROP_PATTERN.matcher(property.name()).matches()) continue;
|
||||
+ gameProfile.getProperties().put(property.name(), property);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return gameProfile;
|
||||
+ }
|
||||
+ // Spigot end
|
||||
|
||||
public static enum State {
|
||||
HELLO,
|
||||
@@ -261,6 +_,7 @@
|
||||
AUTHENTICATING,
|
||||
NEGOTIATING,
|
||||
VERIFYING,
|
||||
+ WAITING_FOR_COOKIES, // CraftBukkit
|
||||
WAITING_FOR_DUPE_DISCONNECT,
|
||||
PROTOCOL_SWITCHING,
|
||||
ACCEPTED;
|
@@ -0,0 +1,12 @@
|
||||
--- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
|
||||
+++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
|
||||
@@ -36,7 +_,8 @@
|
||||
this.connection.disconnect(DISCONNECT_REASON);
|
||||
} else {
|
||||
this.hasRequestedStatus = true;
|
||||
- this.connection.send(new ClientboundStatusResponsePacket(this.status));
|
||||
+ // this.connection.send(new ClientboundStatusResponsePacket(this.status));
|
||||
+ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(net.minecraft.server.MinecraftServer.getServer(), this.connection); // Paper - handle status request
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user