Skip to content

Commit 536aa00

Browse files
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>
1 parent e81974c commit 536aa00

6 files changed

Lines changed: 74 additions & 21 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,6 @@ tun2proxy = { version = "0.7", default-features = false, features = ["udpgw"] }
100100
# Used in mitm tests to sanity-check the cert extensions we emit.
101101
x509-parser = "0.16"
102102

103-
# Temporary patch: adds udpgw_server parameter to the Android JNI run()
104-
# function. Upstream PR: https://github.com/tun2proxy/tun2proxy/pull/247
105-
# Remove this section once tun2proxy >= 0.8 ships with the change.
106-
[patch.crates-io]
107-
tun2proxy = { git = "https://github.com/yyoyoian-pixel/tun2proxy", branch = "feat/udpgw-jni-param" }
108-
109103
[profile.release]
110104
panic = "abort"
111105
codegen-units = 1

android/app/src/main/java/com/github/shadowsocks/bg/Tun2proxy.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ object Tun2proxy {
5959
tunMtu: Char,
6060
verbosity: Int,
6161
dnsStrategy: Int,
62-
udpgwServer: String,
6362
): Int
6463

6564
/** Signals the running `run()` to shut down. Idempotent. */

android/app/src/main/java/com/therealaleph/mhrv/MhrvVpnService.kt

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -259,21 +259,20 @@ class MhrvVpnService : VpnService() {
259259
// the sole owner once it's running.
260260
val detachedFd = parcelFd.detachFd()
261261
tun2proxyRunning.set(true)
262-
// In full mode, enable udpgw so UDP traffic (DNS, QUIC, …) is
263-
// forwarded through the tunnel-node's native udpgw handler.
264-
// 198.18.0.1:7300 is a magic address the tunnel-node intercepts.
265-
val udpgwAddr = if (cfg.mode == Mode.FULL) "198.18.0.1:7300" else ""
262+
// Use tun2proxy_run_with_cli_args C API via dlsym — gives full
263+
// CLI flexibility including --udpgw-server, no fork needed.
264+
val cliArgs = buildString {
265+
append("tun2proxy")
266+
append(" --proxy socks5://127.0.0.1:$socks5Port")
267+
append(" --tun-fd $detachedFd")
268+
append(" --dns virtual")
269+
append(" --verbosity info")
270+
append(" --close-fd-on-drop true")
271+
if (cfg.mode == Mode.FULL) append(" --udpgw-server 198.18.0.1:7300")
272+
}
266273
val worker = Thread({
267274
try {
268-
val rc = Tun2proxy.run(
269-
"socks5://127.0.0.1:$socks5Port",
270-
detachedFd,
271-
/* closeFdOnDrop = */ true,
272-
MTU.toChar(),
273-
/* verbosity = info */ 3,
274-
/* dnsStrategy = virtual */ 0,
275-
udpgwAddr,
276-
)
275+
val rc = Native.runTun2proxy(cliArgs, MTU)
277276
Log.i(TAG, "tun2proxy exited rc=$rc")
278277
} catch (t: Throwable) {
279278
Log.e(TAG, "tun2proxy crashed: ${t.message}", t)

android/app/src/main/java/com/therealaleph/mhrv/Native.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,14 @@ object Native {
9595
* Cheap — just reads atomics. Safe to poll on a second-scale timer.
9696
*/
9797
external fun statsJson(handle: Long): String
98+
99+
/**
100+
* Start tun2proxy via its CLI args C API (`tun2proxy_run_with_cli_args`).
101+
* Resolved at runtime via dlsym from libtun2proxy.so — no fork needed.
102+
*
103+
* @param cliArgs full CLI string, e.g. "tun2proxy --proxy socks5://... --tun-fd 42 --udpgw-server 198.18.0.1:7300"
104+
* @param tunMtu TUN MTU (typically 1500)
105+
* @return 0 on normal shutdown, negative on error. BLOCKS.
106+
*/
107+
external fun runTun2proxy(cliArgs: String, tunMtu: Int): Int
98108
}

src/android_jni.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,53 @@ pub extern "system" fn Java_com_therealaleph_mhrv_Native_statsJson<'a>(
482482
}));
483483
env.new_string(out).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
484484
}
485+
486+
// ---------------------------------------------------------------------------
487+
// tun2proxy CLI API wrapper (dlsym — no fork or patch needed)
488+
// ---------------------------------------------------------------------------
489+
490+
/// `Native.runTun2proxy(cliArgs, tunMtu)` -> int
491+
///
492+
/// Calls `tun2proxy_run_with_cli_args` from libtun2proxy.so via dlsym.
493+
/// This is the C API the tun2proxy maintainer recommends for callers that
494+
/// need full CLI flexibility (e.g. --udpgw-server). BLOCKS until shutdown.
495+
#[no_mangle]
496+
pub extern "system" fn Java_com_therealaleph_mhrv_Native_runTun2proxy<'a>(
497+
mut env: JNIEnv<'a>,
498+
_class: JClass,
499+
cli_args: JString,
500+
tun_mtu: jni::sys::jint,
501+
) -> jni::sys::jint {
502+
safe(-1, AssertUnwindSafe(|| {
503+
let args_str = jstring_to_string(&mut env, &cli_args);
504+
tracing::info!("runTun2proxy: cli={}", args_str);
505+
506+
unsafe {
507+
use std::ffi::{CStr, CString};
508+
509+
let lib = CString::new("libtun2proxy.so").unwrap();
510+
let handle = libc::dlopen(lib.as_ptr(), libc::RTLD_NOW);
511+
if handle.is_null() {
512+
let err = CStr::from_ptr(libc::dlerror());
513+
tracing::error!("dlopen libtun2proxy.so failed: {:?}", err);
514+
return -10;
515+
}
516+
517+
let sym = CString::new("tun2proxy_run_with_cli_args").unwrap();
518+
let func = libc::dlsym(handle, sym.as_ptr());
519+
if func.is_null() {
520+
let err = CStr::from_ptr(libc::dlerror());
521+
tracing::error!("dlsym tun2proxy_run_with_cli_args: {:?}", err);
522+
libc::dlclose(handle);
523+
return -11;
524+
}
525+
526+
type RunFn = unsafe extern "C" fn(*const std::ffi::c_char, u16, bool) -> i32;
527+
let run: RunFn = std::mem::transmute(func);
528+
let c_args = CString::new(args_str).unwrap();
529+
let rc = run(c_args.as_ptr(), tun_mtu as u16, false);
530+
libc::dlclose(handle);
531+
rc
532+
}
533+
}))
534+
}

0 commit comments

Comments
 (0)