Significant release covering builds 53-59 since v1.0-build52. Two
high-impact features: PoW captcha success rate climbs from ~6% to
~56-83% by capturing the real browser_fp from WKWebView and reusing
it; Disconnect button now responds in ~2 seconds instead of ~26s by
fixing a WG/proxy shutdown deadlock.
Builds 53-54 — VK turn_server.urls multi-address parsing (no rotation)
- VK's vchat.joinConversationByLink response includes a turn_server.urls
array — typically 2 endpoints on different /24 subnets (confirmed:
91.231.135.146:19302 and 95.163.34.164:19302). Until now we took
only urls[0]; we now parse them all into TURNCreds.Addresses. - Per-conn rotation evaluated and rejected — iOS includeAllNetworks=true
allows only one exempt host (NEVPNProtocol.serverAddress), and
routing the second TURN endpoint through the tunnel collapses
throughput to ~0.5 Mbps via recursive routing. Multi-address parsing
kept for diagnostic logging and possible future failover use.
Build 55-59 — captcha auto-solve from real browser fingerprint
- Diagnosis from vpn.wifi.0.log analysis: our generated browser_fp +
canned device descriptor get labeled BOT by VK on 62/66 fresh-fetch
attempts (94% failure rate). Generated values can't pass VK's
anti-bot scoring; only a real browser's computed fingerprint can. - New mechanism: CaptchaWKWebView's JS hooks intercept the request
bodies of captchaNotRobot.componentDone (supplies device) and
captchaNotRobot.check (supplies browser_fp). Fields are merged via
VKProfileCache.update into App Group vk_profile.json. - Go-side captcha_pow.go.solveCaptchaPoW loads the saved profile on
every PoW attempt and substitutes the captured browser_fp + device
for the generated values when present. Both main app (pre-bootstrap)
and extension (background grower) read the same file. - Slider improvements (PR #162 commit 2bcb9e35): re-issue componentDone
before getContent (VK responds ERROR without it); fall back to
getContent without captcha_settings if the first try ERRORs
(covers VK's show_type=checkbox-but-actually-slider case). - Validated in vpn.wifi.7.log fresh-start: 5/9 check responses OK
(56% per-check), 5/6 cred fetch sessions succeed first try (83%),
5/5 slots filled in 5.5 minutes from cold cache, ZERO user-visible
captcha. Adapted from cacggghp PR #162 commit Moroka8/vk-turn-proxy@b9642c6 —
re-architected for our WKWebView+App Group model rather than their
HTTP-proxy interceptor.
Build 57 — stopTunnel deadlock fix
- Symptom: pressing Disconnect always took ~26 seconds before iOS
marked the tunnel disconnected. Diagnosed via idevicesyslog: iOS
has a 20s NESMVPNSessionStateStopping timeout + 5s Disposing
timeout = 25s force-kill window when an extension's stopTunnel
doesn't return its completionHandler in time. - Root cause: wgTurnOff called WG device.Close() which blocked
indefinitely waiting for proxy goroutines that were holding TUN
read/write operations the device was trying to close. Classic
deadlock — WG waits on proxy, proxy waits on WG. - Fix in WireGuardBridge/bridge.go wgTurnOff: cancel proxy FIRST
(non-blocking, fires ctx.Done in all 30 conn goroutines + grower- watchdog + stats loops), then close WG device (now unblocks).
Plus pkg/proxy/proxy.go new StopWithTimeout(2s) safety bound.
- watchdog + stats loops), then close WG device (now unblocks).
- Plus PacketTunnelProvider.swift safety-net: completionHandler
guaranteed within 3s via background timer + NSLock guard against
double-call. Belt-and-suspenders against future regressions. - Validated in vpn.wifi.4.log: 336ms total wgTurnOff time, 1.75s
end-to-end disconnect (down from 26s).
UI/UX fixes carried forward from build 52 baseline: nothing new.