Repository navigation
fix(spigot): attribute a tunneled login whose LOGIN_START a packet plugin consumed - #175
Merged
minekube-ai-engineer[bot] merged 1 commit intoSep 28, 2026
Conversation
…ugin consumed A packet-level login plugin (a PacketEvents listener, e.g. an auth plugin's premium autologin) sits upstream of connect-spigot's pipeline position and clears the frame buffer when it cancels LOGIN_START. The connector's own data handler then never sees the packet, its fake offline-login cycle - whose only invocation site is the LOGIN_START branch - never runs, and the session is left pending: the client receives nothing and the only line anywhere is the server's cause-blind "Took too long to log in". Add connect_login_stall_watchdog, installed upstream of connect_data_handler. That position is the point of the handler: connect_data_handler consumes LOGIN_START, so a watchdog installed downstream of it would miss every healthy login and report a stall per session. The watchdog arms a bounded 10 s window when the handshake is observed (well inside the server's own ~30 s login timeout, so its line is the first one the operator reads) and, if no LOGIN_START follows, emits exactly one default-verbosity warning that names the player (or <bedrock-principal-v2>), the session and endpoint correlator, the missing login start, the probable cause and the passthrough caveat: connect-player never fires for passthrough sessions, so a connect-player exemption is not a fix on an offline-mode endpoint. Log-first by design: no packet is consumed, replaced, delayed or fabricated and no channel is closed, so healthy sessions are unaffected; a kick that names the cause changes user-visible behaviour and is deliberately left to a separate decision. Packets are identified by class name (the convention of SpigotChatSessionPacketFilter), not through the server-internals reflection in ClassNames, which keeps the handler testable without a running server. Also expose SessionProposal#getSessionId() - protobuf is an implementation dependency of core, so platform modules cannot resolve the generated types - and extract SpigotInjector.injectsAddons() so the passthrough gate that keeps the watchdog out of offline-mode endpoints is covered by a unit test. RED-first: the new tests fail on the unfixed tree for the right reason (no line at all: "expected: <1> but was: <0>"), then pass; ./gradlew --no-daemon build is green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
A packet-level login plugin (a PacketEvents listener — e.g. an auth plugin's premium autologin) sits upstream of connect-spigot's pipeline position and clears the frame buffer (
PacketEventsImplHelper.handleServerBoundPacket) when it cancelsLOGIN_START. The connector's own data handler then never sees the packet, so its fake offline-login cycle — whose only invocation site is theLOGIN_STARTbranch — never runs. The session is left pending: the client receives nothing and the only line anywhere is the server's cause-blindTook too long to log in(~30 s).Verified live on Paper 26.3-49 + connect-spigot 0.15.15 + packetevents 2.14.0 (pipeline dump: plugin decoder at index 4, the connector's position at 10). Ordering claim, pipeline dumps and the rejected alternatives (a fabricated-login floor = auth bypass; re-assert = nothing to restore) are recorded on the internal verification card that produced this change, and the integrator-facing docs half shipped separately as minekube/connect#175.
The change
connect_login_stall_watchdog, installed upstream ofconnect_data_handler. The position is the point of the handler:SpigotDataHandlerconsumesLOGIN_START(it does not forward it), so a watchdog installed downstream of it would miss every healthy login and report a stall per session. It anchors on the data handler's name (SpigotDataHandler.HANDLER_NAME), so the order does not depend on the order of the surrounding pipeline edits, and the ordering itself is pinned by a test.It arms a bounded window when the handshake is observed and, if no
LOGIN_STARTfollows, emits exactly one default-verbosity warning:(the rendered line above is produced by the test run through the production format string and the production message formatter,
player=/session=/endpoint=mirroring the correlatorsLocalChannelInboundHandleralready logs).Also in this PR:
SessionProposal#getSessionId()— protobuf is animplementationdependency ofcore, so platform modules cannot resolve the generated types at compile time (the first version of this change did not compile inspigotfor exactly that reason). Anything a platform module has to log or correlate now goes through an accessor.SpigotInjector.injectsAddons()— the passthrough gate extracted so it is unit-tested rather than inline in an anonymous handler.ClientIntentionPacket/ServerboundHelloPacket(the convention already used for the chat packets: the handler identifies packets by class name, so the tests need no running server).Evidence — RED first, in a disposable pod (never the Hermes pod)
Clean base
origin/main(dbe4c77d) + JDK 17,./gradlew --no-daemon build:RED-A — the raw unfixed tree (production source at
origin/main, new tests present):i.e. the released tree has no stall detector at all.
RED-B — the unfixed behaviour (handler installed at its production position, observing the same packets, but with no bounded window and no line):
Failing for the right reason: no line at all — exactly the silence the released connector has.
GREEN — the same tests at the PR head:
GREEN — the CI command on the PR head:
./gradlew --no-daemon build→BUILD SUCCESSFUL in 2m 40s(EXIT=0, 73 tasks) on JDK 17 (eclipse-temurin:17-jdk, 6Gi/2 CPU sandbox pod inhermes-minekube, the pod the whole run happened in;jqhad to be installed forReleaseAssetVerificationTest, which is environmental and present on CI's runners).The reverse case is a real assertion, not a mirror of the positive one: the fixture puts a handler at
connect_data_handlerthat consumesLOGIN_STARTexactly like the real one, and the test asserts the watchdog still saw it and that thepacket_handlersink did not — the ordering that makes a healthy login invisible to a downstream installer.Compatibility notes
10 s) that is cancelled byLOGIN_START, by channel close or by handler removal.warn, default verbosity (no debug flag needed, no per-packet line).<bedrock-principal-v2>when the session carries a signed Bedrock principal (same mapping as the existing data-handler/backend-channel lines).600 ticks) login timeout so this line is the first cause-naming one an operator reads; a healthy handshake→LOGIN_STARTgap is milliseconds.PacketHandshakingInSetProtocol/PacketLoginInStart) thatClassNamesfalls back to. Keep the two lists in sync if a Minecraft version moves the packets again — the handler documents the link toClassNames.SpigotInjector.injectsAddons, now tested), so nothing connector-side can observe or attribute such a session — which is exactly the caveat the emitted line states.Follow-up (deliberately not in this change)
LOGIN_START; the handshake variant is flagged, not asserted.Out of scope
/loginand cannot answer that plugin's client-side handshake.