PaperMC/paper-server/patches/sources/net/minecraft/network/Connection.java.patch

305 lines
18 KiB
Diff
Raw Normal View History

--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
2024-12-14 21:47:45 +01:00
@@ -74,13 +_,13 @@
public static final Marker PACKET_RECEIVED_MARKER = Util.make(MarkerFactory.getMarker("PACKET_RECEIVED"), marker -> marker.add(PACKET_MARKER));
public static final Marker PACKET_SENT_MARKER = Util.make(MarkerFactory.getMarker("PACKET_SENT"), marker -> marker.add(PACKET_MARKER));
public static final Supplier<NioEventLoopGroup> NETWORK_WORKER_GROUP = Suppliers.memoize(
- () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Client IO #%d").setDaemon(true).build())
+ () -> 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(
- () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build())
+ () -> 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(
- () -> new DefaultEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Local Client IO #%d").setDaemon(true).build())
+ () -> 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;
2024-12-14 21:47:45 +01:00
@@ -88,6 +_,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
2024-12-14 21:47:45 +01:00
@@ -106,6 +_,40 @@
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
+ @Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address
2024-12-14 21:47:45 +01:00
public Connection(PacketFlow receiving) {
this.receiving = receiving;
@@ -116,6 +_,9 @@
super.channelActive(context);
this.channel = context.channel();
this.address = this.channel.remoteAddress();
+ // Spigot Start
+ this.preparing = false;
+ // Spigot End
if (this.delayedDisconnect != null) {
this.disconnect(this.delayedDisconnect);
}
2024-12-14 21:47:45 +01:00
@@ -128,14 +_,31 @@
2024-12-14 21:47:45 +01:00
@Override
public void exceptionCaught(ChannelHandlerContext context, Throwable exception) {
+ // Paper start - Handle large packets disconnecting client
2024-12-14 21:47:45 +01:00
+ if (exception instanceof io.netty.handler.codec.EncoderException && exception.getCause() instanceof PacketEncoder.PacketTooLargeException packetTooLargeException) {
+ final Packet<?> packet = packetTooLargeException.getPacket();
+ if (packet.packetTooLarge(this)) {
2024-12-14 21:47:45 +01:00
+ ProtocolSwapHandler.handleOutboundTerminalPacket(context, packet);
+ return;
+ } else if (packet.isSkippable()) {
2024-12-14 21:47:45 +01:00
+ Connection.LOGGER.debug("Skipping packet due to errors", exception.getCause());
+ ProtocolSwapHandler.handleOutboundTerminalPacket(context, packet);
+ return;
+ } else {
2024-12-14 21:47:45 +01:00
+ exception = exception.getCause();
+ }
+ }
+ // Paper end - Handle large packets disconnecting client
2024-12-14 21:47:45 +01:00
if (exception instanceof SkipPacketException) {
LOGGER.debug("Skipping packet due to errors", exception.getCause());
} else {
2024-12-14 21:47:45 +01:00
boolean flag = !this.handlingFault;
2020-11-14 16:19:52 +01:00
this.handlingFault = true;
if (this.channel.isOpen()) {
+ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - Add API for quit reason
2024-12-14 21:47:45 +01:00
if (exception instanceof TimeoutException) {
LOGGER.debug("Timeout", exception);
2020-11-14 16:19:52 +01:00
+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - Add API for quit reason
2024-12-14 21:47:45 +01:00
this.disconnect(Component.translatable("disconnect.timeout"));
2020-11-14 16:19:52 +01:00
} else {
2024-12-14 21:47:45 +01:00
Component component = Component.translatable("disconnect.genericReason", "Internal Exception: " + exception);
@@ -147,9 +_,11 @@
disconnectionDetails = new DisconnectionDetails(component);
2020-11-14 16:19:52 +01:00
}
+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason
if (flag) {
2024-12-14 21:47:45 +01:00
LOGGER.debug("Failed to sent packet", exception);
- if (this.getSending() == PacketFlow.CLIENTBOUND) {
+ boolean doesDisconnectExist = this.packetListener.protocol() != ConnectionProtocol.STATUS && this.packetListener.protocol() != ConnectionProtocol.HANDSHAKING; // Paper
+ if (this.getSending() == PacketFlow.CLIENTBOUND && doesDisconnectExist) { // Paper
2024-12-14 21:47:45 +01:00
Packet<?> packet = (Packet<?>)(this.sendLoginDisconnect
? new ClientboundLoginDisconnectPacket(component)
: new ClientboundDisconnectPacket(component));
@@ -166,6 +_,7 @@
}
}
}
2024-12-14 21:47:45 +01:00
+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Spigot // Paper
}
2024-12-14 21:47:45 +01:00
@Override
@@ -175,10 +_,60 @@
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
2024-12-14 21:47:45 +01:00
if (packetListener.shouldHandleMessage(packet)) {
try {
2024-12-14 21:47:45 +01:00
genericsFtw(packet, packetListener);
} catch (RunningOnDifferentThreadException var5) {
+ } catch (io.papermc.paper.util.ServerStopRejectedExecutionException ignored) { // Paper - do not prematurely disconnect players on stop
2024-12-14 21:47:45 +01:00
} catch (RejectedExecutionException var6) {
this.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
} catch (ClassCastException var7) {
@@ -385,10 +_,24 @@
}
}
+ 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
2024-12-14 21:47:45 +01:00
if (this.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) {
2024-12-14 21:47:45 +01:00
tickablePacketListener.tick();
+ } // Paper end - Buffer joins to world
}
if (!this.isConnected() && !this.disconnectionHandled) {
2024-12-14 21:47:45 +01:00
@@ -396,7 +_,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) {
2024-12-14 21:47:45 +01:00
@@ -432,12 +_,15 @@
}
2024-12-14 21:47:45 +01:00
public void disconnect(DisconnectionDetails disconnectionDetails) {
+ // Spigot Start
+ this.preparing = false;
+ // Spigot End
if (this.channel == null) {
2024-12-14 21:47:45 +01:00
this.delayedDisconnect = disconnectionDetails;
}
if (this.isConnected()) {
- this.channel.close().awaitUninterruptibly();
+ this.channel.close(); // We can't wait as this may be called from an event loop.
2024-12-14 21:47:45 +01:00
this.disconnectionDetails = disconnectionDetails;
}
}
2024-12-14 21:47:45 +01:00
@@ -584,6 +_,13 @@
}
}
+ // Paper start - add proper async disconnect
+ public void enableAutoRead() {
+ if (this.channel != null) {
+ this.channel.config().setAutoRead(true);
+ }
+ }
+ // Paper end - add proper async disconnect
2024-12-14 21:47:45 +01:00
public void setupCompression(int threshold, boolean validateDecompressed) {
if (threshold >= 0) {
if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder compressionDecoder) {
@@ -597,6 +_,7 @@
2021-04-29 21:19:33 +02:00
} else {
2024-12-14 21:47:45 +01:00
this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(threshold));
2021-04-29 21:19:33 +02:00
}
+ 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");
2024-12-14 21:47:45 +01:00
@@ -605,6 +_,7 @@
2021-04-29 21:19:33 +02:00
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
}
}
2024-12-14 21:47:45 +01:00
@@ -622,6 +_,26 @@
);
packetListener1.onDisconnect(disconnectionDetails);
}
+ this.pendingActions.clear(); // Free up packet queue.
Add PlayerConnectionCloseEvent This event is invoked when a player has disconnected. It is guaranteed that, if the server is in online-mode, that the provided uuid and username have been validated. The event is invoked for players who have not yet logged into the world, whereas PlayerQuitEvent is only invoked on players who have logged into the world. The event is invoked for players who have already logged into the world, although whether or not the player exists in the world at the time of firing is undefined. (That is, whether the plugin can retrieve a Player object using the event parameters is undefined). However, it is guaranteed that this event is invoked AFTER PlayerQuitEvent, if the player has already logged into the world. This event is guaranteed to never fire unless AsyncPlayerPreLoginEvent has been called beforehand, and this event may not be called in parallel with AsyncPlayerPreLoginEvent for the same connection. Cancelling the AsyncPlayerPreLoginEvent guarantees the corresponding PlayerConnectionCloseEvent is never called. The event may be invoked asynchronously or synchronously. As it stands, it is never invoked asynchronously. However, plugins should check Event#isAsynchronous to be future-proof. On purpose, the deprecated PlayerPreLoginEvent event is left out of the API spec for this event. Plugins should not be using that event, and how PlayerPreLoginEvent interacts with PlayerConnectionCloseEvent is undefined. == AT == public net.minecraft.server.network.ServerLoginPacketListenerImpl$State public net.minecraft.server.network.ServerLoginPacketListenerImpl state
2018-10-07 21:05:28 +02:00
+ // Paper start - Add PlayerConnectionCloseEvent
+ 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();
+ }
+ }
Add PlayerConnectionCloseEvent This event is invoked when a player has disconnected. It is guaranteed that, if the server is in online-mode, that the provided uuid and username have been validated. The event is invoked for players who have not yet logged into the world, whereas PlayerQuitEvent is only invoked on players who have logged into the world. The event is invoked for players who have already logged into the world, although whether or not the player exists in the world at the time of firing is undefined. (That is, whether the plugin can retrieve a Player object using the event parameters is undefined). However, it is guaranteed that this event is invoked AFTER PlayerQuitEvent, if the player has already logged into the world. This event is guaranteed to never fire unless AsyncPlayerPreLoginEvent has been called beforehand, and this event may not be called in parallel with AsyncPlayerPreLoginEvent for the same connection. Cancelling the AsyncPlayerPreLoginEvent guarantees the corresponding PlayerConnectionCloseEvent is never called. The event may be invoked asynchronously or synchronously. As it stands, it is never invoked asynchronously. However, plugins should check Event#isAsynchronous to be future-proof. On purpose, the deprecated PlayerPreLoginEvent event is left out of the API spec for this event. Plugins should not be using that event, and how PlayerPreLoginEvent interacts with PlayerConnectionCloseEvent is undefined. == AT == public net.minecraft.server.network.ServerLoginPacketListenerImpl$State public net.minecraft.server.network.ServerLoginPacketListenerImpl state
2018-10-07 21:05:28 +02:00
+ // Paper end - Add PlayerConnectionCloseEvent
}
}
2024-12-14 21:47:45 +01:00
}