feat: enable udpgw via tun2proxy CLI API — no fork needed (#271)

Uses tun2proxy_run_with_cli_args (the C API) via dlsym instead of
modifying the JNI run() signature. The upstream tun2proxy maintainer
recommended this path — the CLI API accepts --udpgw-server natively.

- Cargo.toml: enable udpgw feature, remove [patch.crates-io]
- MhrvVpnService.kt: build CLI args with --udpgw-server in full mode
- Native.kt + android_jni.rs: dlsym wrapper for the C API
- Tun2proxy.kt: reverted to upstream signature

No fork, no patch, no submodule.

Co-authored-by: yyoyoian-pixel <279225925+yyoyoian-pixel@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yyoyoian-pixel
2026-04-26 20:13:20 +02:00
committed by GitHub
parent e81974c204
commit 536aa0078f
6 changed files with 74 additions and 21 deletions
@@ -59,7 +59,6 @@ object Tun2proxy {
tunMtu: Char,
verbosity: Int,
dnsStrategy: Int,
udpgwServer: String,
): Int
/** Signals the running `run()` to shut down. Idempotent. */
@@ -259,21 +259,20 @@ class MhrvVpnService : VpnService() {
// the sole owner once it's running.
val detachedFd = parcelFd.detachFd()
tun2proxyRunning.set(true)
// In full mode, enable udpgw so UDP traffic (DNS, QUIC, …) is
// forwarded through the tunnel-node's native udpgw handler.
// 198.18.0.1:7300 is a magic address the tunnel-node intercepts.
val udpgwAddr = if (cfg.mode == Mode.FULL) "198.18.0.1:7300" else ""
// Use tun2proxy_run_with_cli_args C API via dlsym — gives full
// CLI flexibility including --udpgw-server, no fork needed.
val cliArgs = buildString {
append("tun2proxy")
append(" --proxy socks5://127.0.0.1:$socks5Port")
append(" --tun-fd $detachedFd")
append(" --dns virtual")
append(" --verbosity info")
append(" --close-fd-on-drop true")
if (cfg.mode == Mode.FULL) append(" --udpgw-server 198.18.0.1:7300")
}
val worker = Thread({
try {
val rc = Tun2proxy.run(
"socks5://127.0.0.1:$socks5Port",
detachedFd,
/* closeFdOnDrop = */ true,
MTU.toChar(),
/* verbosity = info */ 3,
/* dnsStrategy = virtual */ 0,
udpgwAddr,
)
val rc = Native.runTun2proxy(cliArgs, MTU)
Log.i(TAG, "tun2proxy exited rc=$rc")
} catch (t: Throwable) {
Log.e(TAG, "tun2proxy crashed: ${t.message}", t)
@@ -95,4 +95,14 @@ object Native {
* Cheap — just reads atomics. Safe to poll on a second-scale timer.
*/
external fun statsJson(handle: Long): String
/**
* Start tun2proxy via its CLI args C API (`tun2proxy_run_with_cli_args`).
* Resolved at runtime via dlsym from libtun2proxy.so — no fork needed.
*
* @param cliArgs full CLI string, e.g. "tun2proxy --proxy socks5://... --tun-fd 42 --udpgw-server 198.18.0.1:7300"
* @param tunMtu TUN MTU (typically 1500)
* @return 0 on normal shutdown, negative on error. BLOCKS.
*/
external fun runTun2proxy(cliArgs: String, tunMtu: Int): Int
}