Repository navigation
Conversation
A tuic, hysteria or hysteria2 session authenticates once and then multiplexes every later stream over the same QUIC connection without consulting the user table again. UpdateUsers only replaced that table, so a user removed from it kept full access over the connection it already had: closing the individual relayed streams achieves nothing, the client simply opens new ones on the same authenticated session. With tuic's server-side heartbeat refreshing the idle timer, that can last as long as the client cares to keep the connection. Track authenticated sessions per service and close the ones whose user is gone from the new table.
shenaba
force-pushed
the
close-removed-user-sessions
branch
from
September 11, 2026 02:34
d764407 to
4d5883b
Compare
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.
Service.UpdateUsersreplaces the user table, but a tuic / hysteria / hysteria2 session authenticates once and then multiplexes every later stream over the same QUIC connection without consulting that table again:handleUniStreamsetsauthUserand closesauthDone;handleStreamonly waits onauthDone.handleConnectionsetsauthUserafter the client hello;loopStreamsnever re-checks.ServeHTTPsetsauthenticatedand short-circuits on every later auth request;dispatchStreamonly tests that flag.So dropping a user from the table does not revoke the access that user already has, and a caller that needs to cut someone off — quota exhausted, subscription expired, credentials revoked — has no way to do it. Closing the individual relayed streams achieves nothing: the client immediately opens new ones on the same authenticated session. There is no exported handle on the session or on its
*quic.Conn, so the only remaining option is tearing down the whole listener, which disconnects every other user on it.How long the revoked session then survives is entirely up to the client. For tuic it need not end at all on its own:
loopHeartbeatskeeps sending ack-eliciting datagrams, so the idle timeout never fires.(Found from a sing-box-based panel that calls
UpdateUsersto disable a user whose traffic quota ran out. The user stays connected and keeps consuming; with shadowsocks or plain vless the same operation cuts them off at once, because there the transport and the relayed connection are the same thing.)Change
Each
Servicenow keeps the set of its authenticated sessions, andUpdateUserscloses the ones whose user is gone from the new table. Sessions are registered only after authentication — before that there is no user to match a removed one against — and unregistered when they end. Victims are collected under the session lock and closed after it is released, so the close path never runs with that lock held.No API change.
UpdateUsersis the only entry point, and sing-box calls it fromNewInboundbeforeStart, when no session exists yet, so nothing changes for a caller that never removes a user.Test
tuic/service_test.gobrings a service and a client up over loopback UDP, relays a stream, removes the user, and requires the already-established connection to fail. It fails on main withsession of a removed user stayed aliveand passes with this change; also clean under-race -count=3.It is the first test in the repo, so
make testcompiled and ran nothing before this. The scaffolding it needs (a self-signed certificate and anaTLS.ServerConfigovercrypto/tls) is kept inside the one file rather than made into a shared helper, since hysteria and hysteria2 are separate packages and would each need their own copy; happy to add those two as well, or to drop the test entirely if you would rather keep the repo test-free.