Skip to content

fix: close sessions of removed users on UpdateUsers - #4

Open
shenaba wants to merge 1 commit into
anytls:mainfrom
shenaba:close-removed-user-sessions
Open

shenaba wants to merge 1 commit into
anytls:mainfrom
shenaba:close-removed-user-sessions

Conversation

@shenaba

@shenaba shenaba commented Sep 11, 2026

Copy link
Copy Markdown

Service.UpdateUsers replaces 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 to onNewStream without 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 UpdateUsers to 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

Service now keeps the session of each authenticated connection with the user it belongs to, and UpdateUsers closes the ones whose user is gone from the new table. Victims are collected under the session lock and closed after it is released, so Session.Close never runs with that lock held.

No API change. UpdateUsers is the only entry point, and nothing changes for a caller that never removes a user.

The user lookup in NewConnection is rewritten from if user, ok := ...; ok { } else { } into the early-return form, because the name is now needed after that block as well.

Test

service_test.go runs a client and the service over a net.Pipe, opens a proxied stream, removes the user, and requires the already-established stream to fail. It fails on main with session of a removed user stayed alive and passes with this change; go test -race -count=3 ./... is clean.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant