diff --git a/src/main/java/com/zenith/network/SKeepAliveTask.java b/src/main/java/com/zenith/network/SKeepAliveTask.java index d7bbcf98a..c13a42eaa 100644 --- a/src/main/java/com/zenith/network/SKeepAliveTask.java +++ b/src/main/java/com/zenith/network/SKeepAliveTask.java @@ -5,7 +5,10 @@ import org.geysermc.mcprotocollib.protocol.data.ProtocolState; import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.ClientboundKeepAlivePacket; +import java.util.concurrent.TimeUnit; + import static com.zenith.Globals.CONFIG; +import static com.zenith.Globals.SERVER_LOG; public class SKeepAliveTask implements Runnable { private final ServerSession session; @@ -16,9 +19,24 @@ public SKeepAliveTask(ServerSession session) { @Override public void run() { - if (!session.isSpectator() && CONFIG.client.keepAliveHandling.keepAliveMode != KeepAliveMode.INDEPENDENT) return; var state = session.getPacketProtocol().getOutboundState(); if (state != ProtocolState.CONFIGURATION && state != ProtocolState.GAME) return; + // detect half-open connections that are no longer responding to keep alives + // e.g. a controlling player whose internet connection has silently dropped + // disconnecting them lets the bot take over and resync teleports before the destination server kicks us + if (isKeepAliveTimedOut()) { + SERVER_LOG.info("[{}] Disconnecting session that has not responded to keep alives", session.getName()); + session.disconnect("Timed out"); + return; + } + if (!session.isSpectator() && CONFIG.client.keepAliveHandling.keepAliveMode != KeepAliveMode.INDEPENDENT) return; this.session.send(new ClientboundKeepAlivePacket(System.currentTimeMillis())); } + + private boolean isKeepAliveTimedOut() { + if (!CONFIG.server.extra.timeout.enable) return false; + if (!session.isLoggedIn()) return false; + var elapsed = System.currentTimeMillis() - session.getLastKeepAliveResponseTime(); + return elapsed > TimeUnit.SECONDS.toMillis(CONFIG.server.extra.timeout.seconds); + } } diff --git a/src/main/java/com/zenith/network/server/ServerSession.java b/src/main/java/com/zenith/network/server/ServerSession.java index 8ac8c04d0..6da77a9c5 100644 --- a/src/main/java/com/zenith/network/server/ServerSession.java +++ b/src/main/java/com/zenith/network/server/ServerSession.java @@ -86,6 +86,9 @@ public class ServerSession extends TcpServerSession { protected long ping = 0L; protected long lastKeepAliveId = 0L; protected long lastKeepAliveTime = 0L; + // last time we received a keep alive response from this session + // used to detect and disconnect half-open connections, e.g. after a controller's internet outage + protected volatile long lastKeepAliveResponseTime = System.currentTimeMillis(); protected boolean whitelistChecked = false; protected boolean isPlayer = false; @@ -290,6 +293,7 @@ public void disconnect(@Nullable final Component reason, final Throwable cause) public void setLoggedIn() { this.isLoggedIn = true; + this.lastKeepAliveResponseTime = System.currentTimeMillis(); if (!Proxy.getInstance().getActiveConnections().contains(this)) Proxy.getInstance().getActiveConnections().add(this); } diff --git a/src/main/java/com/zenith/network/server/handler/player/incoming/SPlayerKeepAliveHandler.java b/src/main/java/com/zenith/network/server/handler/player/incoming/SPlayerKeepAliveHandler.java index e2be82bc9..3e9d2962c 100644 --- a/src/main/java/com/zenith/network/server/handler/player/incoming/SPlayerKeepAliveHandler.java +++ b/src/main/java/com/zenith/network/server/handler/player/incoming/SPlayerKeepAliveHandler.java @@ -10,6 +10,7 @@ public class SPlayerKeepAliveHandler implements PacketHandler