fix: SOCKS5 UDP ignores authentication, and IN-USER never matches - #3108
Open
IsoLeyN wants to merge 2 commits into
Open
fix: SOCKS5 UDP ignores authentication, and IN-USER never matches#3108IsoLeyN wants to merge 2 commits into
IsoLeyN wants to merge 2 commits into
Conversation
IsoLeyN
marked this pull request as ready for review
August 14, 2026 09:07
wwqgtxx
force-pushed
the
Alpha
branch
2 times, most recently
from
August 15, 2026 02:30
3c70e67 to
7259bbb
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.
Two bugs on the UDP path, same root cause:
authenticationset, anyone who can reach the UDP port can still relay through it.lan-allowed-ips/lan-disallowed-ipsaren't checked there either.IN-USERrules never match UDP, becausemetadata.InUseris always empty.The second one hurts more. If you route per user over TCP, the same client's UDP quietly goes somewhere else. When your catch-all is
DIRECTthat's a leak: TCP is tunnelled while QUIC and plain DNS leave on the real IP. Chrome talking to Google over HTTP/3 is enough to hit it.Why
Auth happens on the TCP control connection. The datagrams then arrive on a separate UDP socket carrying no credentials of their own, so that control connection is the only thing tying them to a user, and per RFC 1928 §4 it's supposed to live as long as the association does.
We authenticate, then throw the result away:
That leaves
listener/socks/udp.gowith nothing to work with, and it doesn't try anyway: no authenticator, no auth store, noinbound.WithInUseranywhere in the file. Every datagram hits the rule engine withInUser == "". The TCP path right next to it does the correct thing:Same asymmetry shows up in
inbound.SkipAuthRemoteAddr, called fromsocks/tcp.go,http/server.goandmixed/mixed.go, all TCP. No UDP listener touches auth at all.Reproduce
Set
authentication, putIN-USER,<user>,DIRECTaboveMATCH,REJECT, then send a DNS query over SOCKS5 UDP as that user. The rule should match and the query should resolve. It doesn't: the datagram falls through toMATCHand gets rejected.For the bypass, change the catch-all to
MATCH,DIRECTand fire a SOCKS5 UDP datagram straight at the port, with no TCP handshake and no credentials. It gets relayed.The fix
association.go(new): refcounted peer -> user table.tcp.go: register the user on UDP ASSOCIATE, release it when the control connection closes. The connection is still drained exactly as before.udp.go: run the same admission checks the TCP listener runs, then look the peer up. If auth is on and there's no association, drop the datagram and return its buffer; otherwise attachWithInUser.additionsis copied before appending, the wayshadowsocks/tcp.goandsnell/server.goalready do it, so the shared listener slice never gets mutated from a per-packet goroutine.Heads up
With
authenticationon, a UDP peer now has to complete UDP ASSOCIATE first. Normal clients already do this. Anything that was firing datagrams at the port without associating will stop working, which is the bypass closing.Associations are keyed by IP, not IP+port, since a client's datagrams come from a different source port than its control connection. Two users behind one IP can't be told apart and the newest association wins. Fine on a LAN, but worth knowing.