PaperMC/paper-server/patches/sources/net/minecraft/network/Connection.java.patch
Shane Freeder 26fe3d0cff Fix premature player kicks on shutdown
When the server is stopping, the default execution handler method will throw a
RejectedExecutionException in order to prevent further execution, this causes
us to lose the actual kick reason. To mitigate this, we'll use a seperate marked
class in order to gracefully ignore these.
2024-04-11 16:37:44 +01:00

287 lines
16 KiB
Diff

--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -82,13 +82,13 @@
marker.add(Connection.PACKET_MARKER);
});
public static final Supplier<NioEventLoopGroup> NETWORK_WORKER_GROUP = Suppliers.memoize(() -> {
- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build());
+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
public static final Supplier<EpollEventLoopGroup> NETWORK_EPOLL_WORKER_GROUP = Suppliers.memoize(() -> {
- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build());
+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
public static final Supplier<DefaultEventLoopGroup> LOCAL_WORKER_GROUP = Suppliers.memoize(() -> {
- return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build());
+ return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
private static final ProtocolInfo<ServerHandshakePacketListener> INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND;
private final PacketFlow receiving;
@@ -96,6 +96,11 @@
private final Queue<Consumer<Connection>> pendingActions = Queues.newConcurrentLinkedQueue();
public Channel channel;
public SocketAddress address;
+ // Spigot Start
+ public java.util.UUID spoofedUUID;
+ public com.mojang.authlib.properties.Property[] spoofedProfile;
+ public boolean preparing = true;
+ // Spigot End
@Nullable
private volatile PacketListener disconnectListener;
@Nullable
@@ -114,7 +119,41 @@
private volatile DisconnectionDetails delayedDisconnect;
@Nullable
BandwidthDebugMonitor bandwidthDebugMonitor;
+ public String hostname = ""; // CraftBukkit - add field
+ // Paper start - NetworkClient implementation
+ public int protocolVersion;
+ public java.net.InetSocketAddress virtualHost;
+ private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); // Paper - Disable explicit network manager flushing
+ // Paper end
+ // Paper start - add utility methods
+ public final net.minecraft.server.level.ServerPlayer getPlayer() {
+ if (this.packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl impl) {
+ return impl.player;
+ } else if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) {
+ org.bukkit.craftbukkit.entity.CraftPlayer player = impl.getCraftPlayer();
+ return player == null ? null : player.getHandle();
+ }
+ return null;
+ }
+ // Paper end - add utility methods
+ // Paper start - packet limiter
+ protected final Object PACKET_LIMIT_LOCK = new Object();
+ protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter(
+ (long)(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.interval() * 1.0e9)
+ ) : null;
+ protected final java.util.Map<Class<? extends net.minecraft.network.protocol.Packet<?>>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>();
+
+ private boolean stopReadingPackets;
+ private void killForPacketSpam() {
+ this.sendPacket(new ClientboundDisconnectPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)), PacketSendListener.thenRun(() -> {
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage));
+ }), true);
+ this.setReadOnly();
+ this.stopReadingPackets = true;
+ }
+ // Paper end - packet limiter
+
public Connection(PacketFlow side) {
this.receiving = side;
}
@@ -123,6 +162,9 @@
super.channelActive(channelhandlercontext);
this.channel = channelhandlercontext.channel();
this.address = this.channel.remoteAddress();
+ // Spigot Start
+ this.preparing = false;
+ // Spigot End
if (this.delayedDisconnect != null) {
this.disconnect(this.delayedDisconnect);
}
@@ -141,8 +183,10 @@
this.handlingFault = true;
if (this.channel.isOpen()) {
+ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - Add API for quit reason
if (throwable instanceof TimeoutException) {
Connection.LOGGER.debug("Timeout", throwable);
+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - Add API for quit reason
this.disconnect((Component) Component.translatable("disconnect.timeout"));
} else {
MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + String.valueOf(throwable));
@@ -155,6 +199,7 @@
disconnectiondetails = new DisconnectionDetails(ichatmutablecomponent);
}
+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason
if (flag) {
Connection.LOGGER.debug("Failed to sent packet", throwable);
if (this.getSending() == PacketFlow.CLIENTBOUND) {
@@ -176,6 +221,7 @@
}
}
+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(throwable); // Spigot // Paper
}
protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet<?> packet) {
@@ -185,11 +231,61 @@
if (packetlistener == null) {
throw new IllegalStateException("Received a packet before the packet listener was initialized");
} else {
+ // Paper start - packet limiter
+ if (this.stopReadingPackets) {
+ return;
+ }
+ if (this.allPacketCounts != null ||
+ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.containsKey(packet.getClass())) {
+ long time = System.nanoTime();
+ synchronized (PACKET_LIMIT_LOCK) {
+ if (this.allPacketCounts != null) {
+ this.allPacketCounts.updateAndAdd(1, time);
+ if (this.allPacketCounts.getRate() >= io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.maxPacketRate()) {
+ this.killForPacketSpam();
+ return;
+ }
+ }
+
+ for (Class<?> check = packet.getClass(); check != Object.class; check = check.getSuperclass()) {
+ io.papermc.paper.configuration.GlobalConfiguration.PacketLimiter.PacketLimit packetSpecificLimit =
+ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.get(check);
+ if (packetSpecificLimit == null || !packetSpecificLimit.isEnabled()) {
+ continue;
+ }
+ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> {
+ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.interval() * 1.0e9));
+ });
+ counter.updateAndAdd(1, time);
+ if (counter.getRate() >= packetSpecificLimit.maxPacketRate()) {
+ switch (packetSpecificLimit.action()) {
+ case DROP:
+ return;
+ case KICK:
+ String deobfedPacketName = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(check.getName());
+
+ String playerName;
+ if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) {
+ playerName = impl.getOwner().getName();
+ } else {
+ playerName = this.getLoggableAddress(net.minecraft.server.MinecraftServer.getServer().logIPs());
+ }
+
+ Connection.LOGGER.warn("{} kicked for packet spamming: {}", playerName, deobfedPacketName.substring(deobfedPacketName.lastIndexOf(".") + 1));
+ this.killForPacketSpam();
+ return;
+ }
+ }
+ }
+ }
+ }
+ // Paper end - packet limiter
if (packetlistener.shouldHandleMessage(packet)) {
try {
Connection.genericsFtw(packet, packetlistener);
} catch (RunningOnDifferentThreadException cancelledpackethandleexception) {
;
+ } catch (io.papermc.paper.util.ServerStopRejectedExecutionException ignored) { // Paper - do not prematurely disconnect players on stop
} catch (RejectedExecutionException rejectedexecutionexception) {
this.disconnect((Component) Component.translatable("multiplayer.disconnect.server_shutdown"));
} catch (ClassCastException classcastexception) {
@@ -205,7 +301,7 @@
}
private static <T extends PacketListener> void genericsFtw(Packet<T> packet, PacketListener listener) {
- packet.handle(listener);
+ packet.handle((T) listener); // CraftBukkit - decompile error
}
private void validateListener(ProtocolInfo<?> state, PacketListener listener) {
@@ -418,12 +514,26 @@
}
}
+ private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world
+ private static int joinAttemptsThisTick; // Paper - Buffer joins to world
+ private static int currTick; // Paper - Buffer joins to world
public void tick() {
this.flushQueue();
+ // Paper start - Buffer joins to world
+ if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) {
+ Connection.currTick = net.minecraft.server.MinecraftServer.currentTick;
+ Connection.joinAttemptsThisTick = 0;
+ }
+ // Paper end - Buffer joins to world
PacketListener packetlistener = this.packetListener;
if (packetlistener instanceof TickablePacketListener tickablepacketlistener) {
+ // Paper start - Buffer joins to world
+ if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener)
+ || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING
+ || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) {
tickablepacketlistener.tick();
+ } // Paper end - Buffer joins to world
}
if (!this.isConnected() && !this.disconnectionHandled) {
@@ -431,7 +541,7 @@
}
if (this.channel != null) {
- this.channel.flush();
+ if (enableExplicitFlush) this.channel.eventLoop().execute(() -> this.channel.flush()); // Paper - Disable explicit network manager flushing; we don't need to explicit flush here, but allow opt in incase issues are found to a better version
}
if (this.tickCount++ % 20 == 0) {
@@ -464,12 +574,15 @@
}
public void disconnect(DisconnectionDetails disconnectionInfo) {
+ // Spigot Start
+ this.preparing = false;
+ // Spigot End
if (this.channel == null) {
this.delayedDisconnect = disconnectionInfo;
}
if (this.isConnected()) {
- this.channel.close().awaitUninterruptibly();
+ this.channel.close(); // We can't wait as this may be called from an event loop.
this.disconnectionDetails = disconnectionInfo;
}
@@ -537,7 +650,7 @@
}
public void configurePacketHandler(ChannelPipeline pipeline) {
- pipeline.addLast("hackfix", new ChannelOutboundHandlerAdapter(this) {
+ pipeline.addLast("hackfix", new ChannelOutboundHandlerAdapter() { // CraftBukkit - decompile error
public void write(ChannelHandlerContext channelhandlercontext, Object object, ChannelPromise channelpromise) throws Exception {
super.write(channelhandlercontext, object, channelpromise);
}
@@ -633,6 +746,7 @@
} else {
this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressionThreshold));
}
+ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
} else {
if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) {
this.channel.pipeline().remove("decompress");
@@ -641,6 +755,7 @@
if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) {
this.channel.pipeline().remove("compress");
}
+ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_DISABLED); // Paper - Add Channel initialization listeners
}
}
@@ -661,6 +776,27 @@
packetlistener1.onDisconnect(disconnectiondetails);
}
+ this.pendingActions.clear(); // Free up packet queue.
+ // Paper start - Add PlayerConnectionCloseEvent
+ final PacketListener packetListener = this.getPacketListener();
+ if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) {
+ /* Player was logged in, either game listener or configuration listener */
+ final com.mojang.authlib.GameProfile profile = commonPacketListener.getOwner();
+ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(),
+ profile.getName(), ((InetSocketAddress) this.address).getAddress(), false).callEvent();
+ } else if (packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginListener) {
+ /* Player is login stage */
+ switch (loginListener.state) {
+ case VERIFYING:
+ case WAITING_FOR_DUPE_DISCONNECT:
+ case PROTOCOL_SWITCHING:
+ case ACCEPTED:
+ final com.mojang.authlib.GameProfile profile = loginListener.authenticatedProfile; /* Should be non-null at this stage */
+ new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(profile.getId(), profile.getName(),
+ ((InetSocketAddress) this.address).getAddress(), false).callEvent();
+ }
+ }
+ // Paper end - Add PlayerConnectionCloseEvent
}
}