From 16869992a86b6b408a8ec8b64f19303265270970 Mon Sep 17 00:00:00 2001 From: Minecrell Date: Fri, 30 Mar 2018 19:00:01 +0200 Subject: [PATCH] Call PaperServerListPingEvent for legacy pings (#1057) * Make the legacy ping handler more reliable The Minecraft server often fails to respond to old ("legacy") pings from old Minecraft versions using the protocol used before the switch to Netty in Minecraft 1.7. Due to packet fragmentation[1], we might not have all needed bytes available when the LegacyPingHandler is called. In this case, it will run into an error, remove the handler and continue using the modern protocol. This is unlikely to happen for the first two revisions of the legacy ping protocol (used in Minecraft 1.5.x and older) since the request consists of only one or two bytes, but happens frequently for the last/third revision introduced in Minecraft 1.6. It has much larger, variable packet sizes due to the inclusion of the virtual host (the hostname/port used to connect to the server). The solution[2] is simple: If we find more than two matching bytes, we buffer the remaining bytes until we have enough to fully read and respond to the request. [1]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-11 [2]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h4-13 * Add legacy ping support to PaperServerListPingEvent Add a new method to StatusClient check if the client is a legacy client that does not support all of the features provided in the event. --- ...-support-to-PaperServerListPingEvent.patch | 33 ++++ ...he-legacy-ping-handler-more-reliable.patch | 157 ++++++++++++++++++ ...ServerListPingEvent-for-legacy-pings.patch | 157 ++++++++++++++++++ scripts/importmcdev.sh | 1 + 4 files changed, 348 insertions(+) create mode 100644 Spigot-API-Patches/0094-Add-legacy-ping-support-to-PaperServerListPingEvent.patch create mode 100644 Spigot-Server-Patches/0282-Make-the-legacy-ping-handler-more-reliable.patch create mode 100644 Spigot-Server-Patches/0283-Call-PaperServerListPingEvent-for-legacy-pings.patch diff --git a/Spigot-API-Patches/0094-Add-legacy-ping-support-to-PaperServerListPingEvent.patch b/Spigot-API-Patches/0094-Add-legacy-ping-support-to-PaperServerListPingEvent.patch new file mode 100644 index 0000000000..456f93e98d --- /dev/null +++ b/Spigot-API-Patches/0094-Add-legacy-ping-support-to-PaperServerListPingEvent.patch @@ -0,0 +1,33 @@ +From d013b9ea523646c41ecb86dbd32488d25073652b Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 19:30:20 +0200 +Subject: [PATCH] Add legacy ping support to PaperServerListPingEvent + +Add a new method to StatusClient check if the client is a legacy +client that does not support all of the features provided in the +event. + +diff --git a/src/main/java/com/destroystokyo/paper/network/StatusClient.java b/src/main/java/com/destroystokyo/paper/network/StatusClient.java +index 517d1523..ffda9f6a 100644 +--- a/src/main/java/com/destroystokyo/paper/network/StatusClient.java ++++ b/src/main/java/com/destroystokyo/paper/network/StatusClient.java +@@ -10,4 +10,16 @@ import com.destroystokyo.paper.event.server.PaperServerListPingEvent; + */ + public interface StatusClient extends NetworkClient { + ++ /** ++ * Returns whether the client is using an older version that doesn't ++ * support all of the features in {@link PaperServerListPingEvent}. ++ * ++ *

For Vanilla, this returns {@code true} for all clients older than 1.7.

++ * ++ * @return {@code true} if the client is using legacy ping ++ */ ++ default boolean isLegacy() { ++ return false; ++ } ++ + } +-- +2.16.2 + diff --git a/Spigot-Server-Patches/0282-Make-the-legacy-ping-handler-more-reliable.patch b/Spigot-Server-Patches/0282-Make-the-legacy-ping-handler-more-reliable.patch new file mode 100644 index 0000000000..8900585eca --- /dev/null +++ b/Spigot-Server-Patches/0282-Make-the-legacy-ping-handler-more-reliable.patch @@ -0,0 +1,157 @@ +From 95527478bf4b3c267b28bf2f82ba0c52185cf9ec Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 18:22:50 +0200 +Subject: [PATCH] Make the legacy ping handler more reliable + +The Minecraft server often fails to respond to old ("legacy") pings +from old Minecraft versions using the protocol used before the switch +to Netty in Minecraft 1.7. + +Due to packet fragmentation[1], we might not have all needed bytes +available when the LegacyPingHandler is called. In this case, it will +run into an error, remove the handler and continue using the modern +protocol. + +This is unlikely to happen for the first two revisions of the legacy +ping protocol (used in Minecraft 1.5.x and older) since the request +consists of only one or two bytes, but happens frequently for the +last/third revision introduced in Minecraft 1.6. + +It has much larger, variable packet sizes due to the inclusion of +the virtual host (the hostname/port used to connect to the server). + +The solution[2] is simple: If we find more than two matching bytes, +we buffer the remaining bytes until we have enough to fully read and +respond to the request. + +[1]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-11 +[2]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h4-13 + +diff --git a/src/main/java/net/minecraft/server/LegacyPingHandler.java b/src/main/java/net/minecraft/server/LegacyPingHandler.java +index 4c1a0181a..f084a653a 100644 +--- a/src/main/java/net/minecraft/server/LegacyPingHandler.java ++++ b/src/main/java/net/minecraft/server/LegacyPingHandler.java +@@ -14,6 +14,7 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + private static final Logger a = LogManager.getLogger(); + private final ServerConnection b; ++ private ByteBuf buf; // Paper + + public LegacyPingHandler(ServerConnection serverconnection) { + this.b = serverconnection; +@@ -21,7 +22,16 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) throws Exception { + ByteBuf bytebuf = (ByteBuf) object; +- ++ // Paper start - Make legacy ping handler more reliable ++ if (this.buf != null) { ++ try { ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ } finally { ++ bytebuf.release(); ++ } ++ return; ++ } ++ // Paper end + bytebuf.markReaderIndex(); + boolean flag = true; + +@@ -50,6 +60,10 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + break; + + default: ++ // Paper start - Replace with improved version below ++ if (bytebuf.readUnsignedByte() != 0x01 || bytebuf.readUnsignedByte() != 0xFA) return; ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ /* + boolean flag1 = bytebuf.readUnsignedByte() == 1; + + flag1 &= bytebuf.readUnsignedByte() == 250; +@@ -73,6 +87,8 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + } finally { + bytebuf1.release(); + } ++ */ ++ // Paper end + } + + bytebuf.release(); +@@ -92,6 +108,74 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + } + ++ // Paper start ++ private void 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); ++ ++ // Short + Short + Byte + Short + Int ++ if (!buf.isReadable(2 + 2 + 1 + 2 + 4)) { ++ return; ++ } ++ ++ short length = buf.readShort(); ++ if (!buf.isReadable(length * 2)) { ++ return; ++ } ++ ++ if (!buf.readBytes(length * 2).toString(StandardCharsets.UTF_16BE).equals("MC|PingHost")) { ++ removeHandler(ctx); ++ return; ++ } ++ ++ if (!buf.isReadable(2)) { ++ return; ++ } ++ ++ length = buf.readShort(); ++ if (!buf.isReadable(length)) { ++ return; ++ } ++ ++ MinecraftServer server = this.b.d(); ++ int protocolVersion = buf.readByte(); ++ length = buf.readShort(); ++ String host = buf.readBytes(length * 2).toString(StandardCharsets.UTF_16BE); ++ int port = buf.readInt(); ++ ++ if (buf.isReadable()) { ++ removeHandler(ctx); ++ return; ++ } ++ ++ buf.release(); ++ this.buf = null; ++ ++ a.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", ++ Byte.MAX_VALUE, server.getVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ this.a(ctx, this.a(response)); ++ } ++ ++ private void removeHandler(ChannelHandlerContext ctx) { ++ ByteBuf buf = this.buf; ++ this.buf = null; ++ ++ buf.resetReaderIndex(); ++ ctx.pipeline().remove("legacy_query"); ++ ctx.fireChannelRead(buf); ++ } ++ // Paper end ++ + private void a(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf) { + channelhandlercontext.pipeline().firstContext().writeAndFlush(bytebuf).addListener(ChannelFutureListener.CLOSE); + } +-- +2.16.2 + diff --git a/Spigot-Server-Patches/0283-Call-PaperServerListPingEvent-for-legacy-pings.patch b/Spigot-Server-Patches/0283-Call-PaperServerListPingEvent-for-legacy-pings.patch new file mode 100644 index 0000000000..f18c64628d --- /dev/null +++ b/Spigot-Server-Patches/0283-Call-PaperServerListPingEvent-for-legacy-pings.patch @@ -0,0 +1,157 @@ +From c3962bc906bb2b19cd37ef2d64d3aaa1a06763d3 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 19:30:51 +0200 +Subject: [PATCH] Call PaperServerListPingEvent for legacy pings + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java +new file mode 100644 +index 000000000..74c012fd4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java +@@ -0,0 +1,73 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import net.minecraft.server.MinecraftServer; ++import org.apache.commons.lang3.StringUtils; ++import org.bukkit.ChatColor; ++ ++import java.net.InetSocketAddress; ++ ++import javax.annotation.Nullable; ++ ++public final class PaperLegacyStatusClient implements StatusClient { ++ ++ private final InetSocketAddress address; ++ private final int protocolVersion; ++ @Nullable private final InetSocketAddress virtualHost; ++ ++ private PaperLegacyStatusClient(InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { ++ this.address = address; ++ this.protocolVersion = protocolVersion; ++ this.virtualHost = virtualHost; ++ } ++ ++ @Override ++ public InetSocketAddress getAddress() { ++ return this.address; ++ } ++ ++ @Override ++ public int getProtocolVersion() { ++ return this.protocolVersion; ++ } ++ ++ @Nullable ++ @Override ++ public InetSocketAddress getVirtualHost() { ++ return this.virtualHost; ++ } ++ ++ @Override ++ public boolean isLegacy() { ++ return true; ++ } ++ ++ public static PaperServerListPingEvent processRequest(MinecraftServer server, ++ InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { ++ ++ PaperServerListPingEvent event = new PaperServerListPingEventImpl(server, ++ new PaperLegacyStatusClient(address, protocolVersion, virtualHost), Byte.MAX_VALUE, null); ++ server.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return null; ++ } ++ ++ return event; ++ } ++ ++ public static String getMotd(PaperServerListPingEvent event) { ++ return getFirstLine(event.getMotd()); ++ } ++ ++ public static String getUnformattedMotd(PaperServerListPingEvent event) { ++ // Strip color codes and all other occurrences of the color char (because it's used as delimiter) ++ return getFirstLine(StringUtils.remove(ChatColor.stripColor(event.getMotd()), ChatColor.COLOR_CHAR)); ++ } ++ ++ private static String getFirstLine(String s) { ++ int pos = s.indexOf('\n'); ++ return pos >= 0 ? s.substring(0, pos) : s; ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/LegacyPingHandler.java b/src/main/java/net/minecraft/server/LegacyPingHandler.java +index f084a653a..39d19e91b 100644 +--- a/src/main/java/net/minecraft/server/LegacyPingHandler.java ++++ b/src/main/java/net/minecraft/server/LegacyPingHandler.java +@@ -9,6 +9,7 @@ import java.net.InetSocketAddress; + import java.nio.charset.StandardCharsets; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import com.destroystokyo.paper.network.PaperLegacyStatusClient; // Paper + + public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + +@@ -41,11 +42,19 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + MinecraftServer minecraftserver = this.b.d(); + int i = bytebuf.readableBytes(); + String s; ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper + + switch (i) { + case 0: + LegacyPingHandler.a.debug("Ping: (<1.3.x) from {}:{}", inetsocketaddress.getAddress(), Integer.valueOf(inetsocketaddress.getPort())); +- s = String.format("%s\u00a7%d\u00a7%d", new Object[] { minecraftserver.getMotd(), Integer.valueOf(minecraftserver.H()), Integer.valueOf(minecraftserver.I())}); ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = PaperLegacyStatusClient.processRequest(minecraftserver, inetsocketaddress, 39, null); ++ if (event == null) { ++ channelhandlercontext.close(); ++ break; ++ } ++ s = String.format("%s\u00a7%d\u00a7%d", PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers()); ++ // Paper end + this.a(channelhandlercontext, this.a(s)); + break; + +@@ -55,7 +64,15 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + } + + LegacyPingHandler.a.debug("Ping: (1.4-1.5.x) from {}:{}", inetsocketaddress.getAddress(), Integer.valueOf(inetsocketaddress.getPort())); +- s = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", new Object[] { Integer.valueOf(127), minecraftserver.getVersion(), minecraftserver.getMotd(), Integer.valueOf(minecraftserver.H()), Integer.valueOf(minecraftserver.I())}); ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = PaperLegacyStatusClient.processRequest(minecraftserver, inetsocketaddress, 61, null); ++ if (event == null) { ++ channelhandlercontext.close(); ++ break; ++ } ++ s = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(), ++ PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers()); ++ // Paper end + this.a(channelhandlercontext, this.a(s)); + break; + +@@ -161,8 +178,16 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + a.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); + +- String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", +- Byte.MAX_VALUE, server.getVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port); ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event = PaperLegacyStatusClient.processRequest( ++ server, (InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost); ++ if (event == null) { ++ ctx.close(); ++ return; ++ } ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(), ++ PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers()); + this.a(ctx, this.a(response)); + } + +-- +2.15.0 + diff --git a/scripts/importmcdev.sh b/scripts/importmcdev.sh index c992a4b5f6..40162024ed 100755 --- a/scripts/importmcdev.sh +++ b/scripts/importmcdev.sh @@ -77,6 +77,7 @@ import IHopper import ItemBlock import ItemFireworks import ItemMonsterEgg +import LegacyPingHandler import NavigationAbstract import NBTTagCompound import NBTTagList