feat: native udpgw without QUIC/DNS - QUIC/DNS with udp associate — stable VoIP, faster browsing - needs new tunnel deployment for udpgw#222
Conversation
Why udpgw is needed even with UDP associate: UDP associate (udp_open/udp_data) creates one tunnel session per UDP destination and polls each independently. On high-latency or shaky networks this compounds — N simultaneous UDP flows need N separate polling loops, each paying its own batch round-trip overhead. Google Meet calls, which fire dozens of concurrent STUN + RTP flows, stall or fail entirely because the per-destination polling can't keep up. udpgw multiplexes ALL UDP over one persistent TCP-like session using conn_id framing. One batch op carries frames for many destinations. Persistent sockets per (conn_id, dest) with continuous reader tasks keep source ports stable — critical for protocols like Telegram VoIP and STUN that expect replies on the same port. Both paths coexist — they serve different traffic: - UDP associate (SOCKS5): apps that negotiate SOCKS5 UDP relay - udpgw (198.18.0.1:7300): TUN-captured UDP (DNS, QUIC, Meet, etc.) tun2proxy vendored as git submodule at v0.7.20 with one transparent commit adding udpgw_server to the Android JNI run() function. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
QUIC through udpgw is slower than TCP/HTTP2 through the batch pipeline — blocking it forces browsers to fall back to TCP, improving YouTube and general browsing speed. DNS is better handled by tun2proxy's virtual DNS / SOCKS5 UDP associate path which is more reliable for single request-response exchanges. VoIP (Telegram, Meet) still flows through udpgw normally. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Hi @yyoyoian-pixel — the udpgw work itself is reasonable (the UDP-associate-vs-udpgw analysis is exactly right; per-destination polling does fall over under Meet-style flow counts). The integration shape is what blocks merge. Concrete issues, in priority order: 1. (blocker) The submodule SHA can't be fetched from the URL declared in
So Could you push the actual commit to 2. (blocker) The stated reason for vendoring is needing # Cargo.toml
[patch.crates-io]
tun2proxy = { git = \"https://github.com/<your-fork>/tun2proxy\", branch = \"udpgw-jni-arg\" }This pins via Cargo.lock to a real, fetchable, content-addressed SHA on a fork we can audit. With the submodule approach, after this lands, whatever bytes happen to live at If you'd like, I can take the udpgw branch you've been working off of, push it to a fork under 3. (bug) Blocking UDP/53 in The rationale ("tun2proxy's virtual DNS handles it more reliably") is correct on Android, where 4. (note, not blocker) The JNI signature change creates fork lock-in until upstream 0.8 lands.
**5. (small) The Net: the udpgw rationale is sound, the wire-protocol code in [reply via Anthropic Claude | reviewed by @therealaleph] |
Use the idiomatic Rust [patch.crates-io] mechanism instead of a git submodule. Points to yyoyoian-pixel/tun2proxy fork with the udpgw JNI parameter patch (upstream PR: tun2proxy/tun2proxy#247). Will be removed once upstream ships the change in tun2proxy >= 0.8. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@therealaleph regarding dns bug on desktop:
I changed the submodule to use the patch in cargo, once the PR is merged in |
also fine with this, we can clean that up in the next PR. |
|
Thanks for the quick rework — that's the right shape. Verified:
Two small asks before merge:
Either is fine. I'd take (a) — happy to fork the branch over today. The remaining notes from my earlier review (JNI fork lock-in comment in Once one of the two ownership options is settled and the lockfile is committed, this is ready to merge from my side. [reply via Anthropic Claude | reviewed by @therealaleph] |
Locks tun2proxy at dfc24ed1 so the patch resolution is recorded and any branch rewrite is visible in the lockfile diff. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
done. |
|
will discord work with this update? |
|
@Feiabyte it should on android. |
what about pc? since discord uses udp for voice it keeps saying no route |
|
@Feiabyte This creates a TUN on your Mac, captures all traffic (including Discord UDP), and routes it through mhrv-rs's SOCKS5 → the tunnel. Same as Android. then your apps and everything will work as well. P.s you need the new tunnel deployed. |
i see. do i run that command on my vps? Im using windows and the vps in ubuntu |
|
it routes all your env to mhrv desktop, in client side. (windows). read about tun2proxy what it does it will help your case. |
I will thanks for the help |
|
@therealaleph talking to claude here :D can we merge? |
Bug:
|
|
@dazzling-no-more thanks! on it |
JoinHandle::drop detaches the task without aborting it. When udpgw_server_task is cancelled (session close), the post-loop cleanup never runs and per-(conn_id, dest) reader tasks become zombies holding Arc<UdpSocket> file descriptors. AbortHandle::drop aborts the task automatically, so cleanup is correct by construction regardless of how the parent task exits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@dazzling-no-more fixed & tested. |
What this does
Adds native udpgw wire protocol to the tunnel-node and wires it through tun2proxy on Android. Blocks QUIC (UDP 443) and DNS (UDP 53) from the udpgw path so browsers use TCP/HTTP2 (faster through the batch pipeline) and DNS uses tun2proxy's virtual DNS (more reliable).
Result: VoIP calls (Telegram, Meet) work through udpgw. Browsing stays fast on TCP.
Needs new tunnel deployment for udpgw.
Why udpgw is needed even with UDP associate
UDP associate creates one tunnel session per UDP destination and polls each independently. On slow or shaky networks:
udpgw tunnels all UDP over one persistent TCP connection. TCP handles retransmission and congestion — the connection stays alive even when the underlying network drops packets. Persistent sockets per
(conn_id, dest)with continuous reader tasks keep source ports stable.Why block QUIC and DNS in udpgw
Changes
tunnel-node/src/udpgw.rs (new, ~500 lines)
(conn_id, dest_addr)with continuous reader taskstokio::io::duplex()— no extra port neededtunnel-node/src/main.rs
SessionWriterenum (TCP or Duplex), genericreader_taskcreate_udpgw_session()for in-process virtual session198.18.0.1:7300intercepted in connect handlersvendor/tun2proxy (git submodule)
udpgw_serverJNI parametertun2proxy = { version = "0.8", features = ["udpgw"] }Android
Tun2proxy.kt:udpgwServer: StringparameterMhrvVpnService.kt: passes198.18.0.1:7300in full modeTest plan
🤖 Generated with Claude Code