Two related changes ported from main, adapted to the flat client/ layout:
1. Identity caching + per-slot TURN creds (vkauth)
- Split the monolithic getTokenChain into:
* acquireVkIdentity — captcha-gated steps 1-3 (anonym_token,
getCallPreview, getAnonymousToken). Cached per (link, client_id)
for identityLifetime=8m, globally serialised via vkRequestMu +
3-6s cooldown.
* acquireVkTurnSlot — lightweight steps 4-5 (auth.anonymLogin
with fresh device_id, vchat.joinConversationByLink). Each call
returns a distinct (username, password) pair, so multiple
streams under the same identity each get their own VK-side
slot — bypasses per-username throttling without re-solving
captcha.
- vkCredentialsList trimmed from 5 to 2: VKVIDEO_* and VK_ID_AUTH_APP
started returning error_code:3 "Unknown method" on
calls.getAnonymousToken (observed 2026-04-28) and only burned
throttle budget if kept in rotation.
- streamsPerCache 10 -> 1: each stream now caches its own slot
creds because slots are unique per call.
- Credential rotation starts at streamID%n offset so concurrent
streams spread across the credential list instead of all hitting
the same client_id first.
- identityStore + identityEntry give per-(link, client_id)
serialisation: only one stream solves captcha per identity.
- turn_server.urls picking is transport-aware (prefers urls whose
?transport= matches udpMode, falls back to the full list when
nothing matches to preserve -port override) and round-robins
within an identity via urlCounter — streamID%len(pool) collapses
every stream of an identity onto the same parity.
2. Multiple TURN allocations per stream (oneTurnConnection)
- New -allocs-per-stream flag (default 1).
- dialTurn extracted as a helper that returns a turnAllocation
(dialConn, turn.Client, relay PacketConn).
- relayPool wraps the live relays with sync.RWMutex + atomic
counter for round-robin pick on the outbound hot path.
- Outbound goroutine (conn2 -> relay) uses pool.pick() round-robin.
Per-relay inbound goroutine (relay -> conn2) is spawned via
spawnInbound; they all feed the same conn2 keyed by
internalPipeAddr.
- Primary allocation opens immediately. Extras are deferred 3s so
the DTLS handshake completes over the primary first, letting the
server install the Connection ID; subsequent multi-path packets
are then matched to the existing session via CID rather than
5-tuple. Each extra is jittered 200ms apart.
- Allocation tracking + deadline-on-cancel + close-on-exit handle
clean shutdown of all relays.
A new udpMode global mirrors the -udp flag so acquireVkTurnSlot
(called from the credential layer, which doesn't have access to
turnParams) can filter URLs by transport.