Conversation
A connection is authenticated once, by the password hash that opens it, and then carries any number of streams without that password being looked at again. UpdateUsers only replaced the user 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 session. Track the session of each authenticated connection 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
bc63099 to
d732103
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 an anytls connection is authenticated once — by the password hash in its first 32 bytes — and then carries any number of streams, each dispatched straight toonNewStreamwithout that password being looked at again.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 session. The session lives as long as its TCP connection, and the only remaining option is tearing down the listener, which disconnects every other user on it.
(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. The same is true of tuic/hysteria/hysteria2 in sing-quic, which I have sent a matching patch for; anytls is affected for the same reason — one authenticated transport, many multiplexed streams.)Change
Servicenow keeps the session of each authenticated connection with the user it belongs to, andUpdateUserscloses the ones whose user is gone from the new table. Victims are collected under the session lock and closed after it is released, soSession.Closenever runs with that lock held.No API change.
UpdateUsersis the only entry point, and nothing changes for a caller that never removes a user.The user lookup in
NewConnectionis rewritten fromif user, ok := ...; ok { } else { }into the early-return form, because the name is now needed after that block as well.Test
service_test.goruns a client and the service over anet.Pipe, opens a proxied stream, removes the user, and requires the already-established stream to fail. It fails on main withsession of a removed user stayed aliveand passes with this change;go test -race -count=3 ./...is clean.