Native UDP: hardening fixes (slot leak, transient errors, rotation, visitor unregister) - #2
Merged
ssharkkky merged 5 commits intoAug 21, 2026
Conversation
NaiveConnectUdpDatagramBackend::Send() inserted a new TargetEntry into targets_ before the association-level admission checks. When those checks rejected the datagram that created the target, Send() returned OK and left a permanent kConnecting entry with an empty outbound queue that no timer ever reaps, leaking one of the 32 target slots for the association's lifetime. A client that bursts many distinct destinations under queue pressure could exhaust the target table and stall further targets. Track whether the target was created for this datagram and erase it on the association-level queue-capacity drop path. The cooldown/retiring branches cannot fire for a freshly created kConnecting target, so only this path needs the cleanup. Not built locally (Chromium tree); change is code-review-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
HandleRelayRead() and OnRelayWriteComplete() called Finish() on any negative result, tearing down the control TCP connection and every active tunnel for the association. A single per-packet condition would therefore kill traffic to all unrelated destinations sharing the association. The most common case is Windows surfacing a prior ICMP "port unreachable" as ERR_CONNECTION_RESET on the next recv/send; ERR_MSG_TOO_BIG and ERR_ADDRESS_UNREACHABLE are similarly per-datagram. Classify these as recoverable: drop the affected datagram and continue pumping instead of finishing the association. Non-recoverable errors still finish. Not built locally (Chromium tree); change is code-review-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CleanUpIdleConnections() rotated TCP tunnels on both idle_timeout_ and a tunnel_timeout_ max-lifetime bound, but UDP associations were only aged by idle_timeout_. A long-lived association carrying steady traffic never rotated, unlike every other tunnel, weakening the intended lifetime cap and connection rotation behavior. Expose Socks5UdpAssociation::creation_time() and apply the same "idle > idle_timeout_ || age > tunnel_timeout_" test used for TCP tunnels. Not built locally (Chromium tree); change is code-review-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Close() unregisters the Http3DatagramVisitor when datagram_visitor_registered_ is set, but OnStreamClosed() cleared the flag without calling UnregisterHttp3DatagramVisitor(). The stream is already closed on this path so the unregister is effectively a no-op today, but the asymmetry is fragile: a future change that lets the stream handle outlive the close notification would leave a dangling visitor registration. Make OnStreamClosed() perform the same guarded unregister as Close(). Not built locally (Chromium tree); change is code-review-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reject new CONNECT-UDP targets before factory/emplace when the association queue is full, so admission pressure cannot churn throwaway tunnels. Recoverable relay SendTo failures drop the datagram without refreshing last_activity_. Add ShouldExpire() for the idle-or-age rotation already used by CleanUpIdleConnections. Cover the four hardening behaviors in naive_connect_udp_backend_test and naive_socks5_udp_association_test. Built and ran the M2/M3 owner scripts on the existing macOS arm64 Release tree: M2_SOCKS5_UDP_INGRESS_OK M3_NATIVE_UDP_CLIENT_OK
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.
Summary
Hardening fixes for the native UDP path on top of
codex/native-udp-foundation.Four small, self-contained changes addressing correctness and availability
issues found while reviewing the SOCKS5 UDP ASSOCIATE → CONNECT-UDP over H3
datagram flow. Each is an independent green-to-green commit.
What's fixed
Target-slot leak on first-datagram admission drop (
naive_connect_udp_datagram_backend.cc)Send()inserted a newTargetEntryintotargets_before theassociation-level admission checks. When those checks rejected the datagram
that created the target,
Send()returnedOKand left a permanentkConnectingentry with an empty queue that no timer reaps — leaking one ofthe 32 target slots for the association's lifetime. A client bursting many
distinct destinations under queue pressure could exhaust the target table.
Fix: erase the freshly created entry on the admission-drop path.
Transient UDP relay error tears down the whole association (
socks5_udp_association.cc)HandleRelayRead()/OnRelayWriteComplete()calledFinish()on anynegative result, killing the control TCP connection and every active tunnel.
A single per-packet condition — most commonly Windows surfacing a prior ICMP
"port unreachable" as
ERR_CONNECTION_RESETon the next recv/send, alsoERR_MSG_TOO_BIG/ERR_ADDRESS_UNREACHABLE— would take down traffic toall unrelated destinations sharing the association. Fix: classify these as
recoverable, drop the affected datagram, keep pumping.
UDP associations never rotate at
tunnel_timeout_(socks5_udp_association.{h,cc},naive_proxy.cc)CleanUpIdleConnections()rotates TCP tunnels on bothidle_timeout_and atunnel_timeout_max-lifetime bound, but UDP associations were only aged byidle_timeout_. A steadily-used association never rotated. Fix: exposecreation_time()and apply the sameidle || agetest used for TCP tunnels.Asymmetric H3 datagram-visitor unregister on stream close (
quic_proxy_datagram_client_socket.cc)Close()guardsUnregisterHttp3DatagramVisitor()behinddatagram_visitor_registered_, butOnStreamClosed()cleared the flagwithout unregistering. It's a no-op today (the stream is already closed on
that path), but the asymmetry is fragile under future refactors. Fix: make
OnStreamClosed()perform the same guarded unregister. Defensive only.Not addressed here (follow-ups)
Deliberately left out of this PR because they involve configuration surface or
authorization-semantics decisions the branch author should own:
NaiveProxy::CanUseNativeUdp()—no server-capability check or explicit disable flag, so a mismatch can silently
black-hole UDP. Needs a config/CLI decision.
BindUdpRelay()— latentunder the shipped
kDefaultcapture mode, but atkEverythingthe SOCKS5datagram destination + payload would be captured. A correct fix needs a
non-capturing NetLog path and touches NetLog semantics.
IsAuthorizedClient()wildcard-port latching — when the client requestedDST port 0, the first datagram from any source port on the control-peer IP
latches
client_endpoint_. Tightening this is an authorization-semanticschange.
Testing
Not built or run locally. Review-only. Suggest building the
naivetarget andrunning the SOCKS5 UDP / CONNECT-UDP unit tests before merge.