From 2f1e89e738045320b4cb77070fe80a7854dd6cc5 Mon Sep 17 00:00:00 2001 From: Alexander Olkhovoy Date: Tue, 7 Apr 2026 12:07:37 +0300 Subject: [PATCH] Add embed_session_info option for Android per-app routing - Add `embed_session_info` boolean field to Args struct with CLI argument - Add `runWithSessionInfo` JNI method in android.rs accepting embed_session_info parameter - Pass embed_session_info flag through SocksProxyManager and SocksProxyImpl constructors - Modify SOCKS5 authentication to embed session info in username field when enabled - Format embedded username as "original_username|protocol|src_ip|src_port" - Enable per-app routing on Android --- src/android.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/args.rs | 12 ++++++++++++ src/lib.rs | 5 +++-- src/socks.rs | 22 +++++++++++++++++++-- 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/android.rs b/src/android.rs index 837485c..2144b74 100644 --- a/src/android.rs +++ b/src/android.rs @@ -58,6 +58,58 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( .resolve::() } +/// # Safety +/// +/// Running tun2proxy with some arguments including embed_session_info option +/// Parameters: +/// - proxy_url: the proxy url, e.g. "socks5://user:pass@127.0.0.1:1080" +/// - tun_fd: the tun file descriptor, it will be owned by tun2proxy +/// - close_fd_on_drop: whether close the tun_fd on drop +/// - tun_mtu: the tun mtu +/// - dns_strategy: the dns strategy, see ArgDns enum +/// - verbosity: the verbosity level, see ArgVerbosity enum +/// - embed_session_info: if true, embeds session info in SOCKS5 username field +/// Format: "original_username|protocol|src_ip|src_port" +/// Use this with getConnectionOwnerUid() for per-app routing on Android 10+ +#[unsafe(no_mangle)] +pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_runWithSessionInfo( + mut env: EnvUnowned<'_>, + _clazz: JClass<'_>, + proxy_url: JString<'_>, + tun_fd: jint, + close_fd_on_drop: jboolean, + tun_mtu: jchar, + verbosity: jint, + dns_strategy: jint, + embed_session_info: jboolean, +) -> jint { + let dns = dns_strategy.try_into().unwrap(); + let verbosity = verbosity.try_into().unwrap(); + let filter_str = &format!("off,tun2proxy={verbosity}"); + let filter = android_logger::FilterBuilder::new().parse(filter_str).build(); + android_logger::init_once( + android_logger::Config::default() + .with_tag("tun2proxy") + .with_max_level(log::LevelFilter::Trace) + .with_filter(filter), + ); + env.with_env(|env: &mut Env| -> Result { + let proxy_url = get_java_string(env, &proxy_url).unwrap(); + let proxy = ArgProxy::try_from(proxy_url.as_str()).unwrap(); + + let mut args = Args::default(); + args.proxy(proxy) + .tun_fd(Some(tun_fd)) + .close_fd_on_drop(close_fd_on_drop) + .dns(dns) + .verbosity(verbosity) + .embed_session_info(embed_session_info != 0); + let v = crate::general_api::general_run_for_api(args, tun_mtu, false); + Ok::(v) + }) + .resolve::() +} + /// # Safety /// /// Shutdown tun2proxy diff --git a/src/args.rs b/src/args.rs index fe87fac..61c649d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -128,6 +128,12 @@ pub struct Args { #[arg(long, value_name = "number", default_value = "200")] pub max_sessions: usize, + /// Embed session info (protocol, src_ip, src_port) in SOCKS5 username field. + /// Format: "original_username|protocol|src_ip|src_port" + /// Useful for per-app routing on Android via getConnectionOwnerUid(). + #[arg(long)] + pub embed_session_info: bool, + /// UDP gateway server address, forwards UDP packets via specified TCP server #[cfg(feature = "udpgw")] #[arg(long, value_name = "IP:PORT")] @@ -188,6 +194,7 @@ impl Default for Args { daemonize: false, exit_on_fatal_error: false, max_sessions: 200, + embed_session_info: false, #[cfg(feature = "udpgw")] udpgw_server: None, #[cfg(feature = "udpgw")] @@ -273,6 +280,11 @@ impl Args { self.setup = setup; self } + + pub fn embed_session_info(&mut self, embed_session_info: bool) -> &mut Self { + self.embed_session_info = embed_session_info; + self + } } #[repr(C)] diff --git a/src/lib.rs b/src/lib.rs index 90f5de5..6f4d68d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -225,9 +225,10 @@ where let socket_queue = None; use socks5_impl::protocol::Version::{V4, V5}; + let embed_session_info = args.embed_session_info; let mgr: Arc = match args.proxy.proxy_type { - ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)), - ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)), + ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key, embed_session_info)), + ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key, embed_session_info)), ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)), ProxyType::None => Arc::new(NoProxyManager::new()), }; diff --git a/src/socks.rs b/src/socks.rs index 7198a68..48daf7c 100644 --- a/src/socks.rs +++ b/src/socks.rs @@ -32,6 +32,7 @@ struct SocksProxyImpl { credentials: Option, command: protocol::Command, udp_associate: Option, + embed_session_info: bool, } impl SocksProxyImpl { @@ -42,6 +43,7 @@ impl SocksProxyImpl { credentials: Option, version: Version, command: protocol::Command, + embed_session_info: bool, ) -> Result { let mut result = Self { server_addr, @@ -56,6 +58,7 @@ impl SocksProxyImpl { credentials, command, udp_associate: None, + embed_session_info, }; result.send_client_hello()?; Ok(result) @@ -170,7 +173,19 @@ impl SocksProxyImpl { fn send_auth_data(&mut self) -> std::io::Result<()> { let tmp = UserKey::default(); let credentials = self.credentials.as_ref().unwrap_or(&tmp); - let request = password_method::Request::new(&credentials.username, &credentials.password); + + let username = if self.embed_session_info { + let proto = match self.command { + protocol::Command::Connect => "tcp", + protocol::Command::UdpAssociate => "udp", + _ => "unknown", + }; + format!("{}|{}|{}|{}", credentials.username, proto, self.info.src.ip(), self.info.src.port()) + } else { + credentials.username.clone() + }; + + let request = password_method::Request::new(&username, &credentials.password); request.write_to_stream(&mut self.server_outbuf)?; self.state = SocksState::ReceiveAuthResponse; Ok(()) @@ -332,6 +347,7 @@ pub(crate) struct SocksProxyManager { server: SocketAddr, credentials: Option, version: Version, + embed_session_info: bool, } #[async_trait::async_trait] @@ -352,16 +368,18 @@ impl ProxyHandlerManager for SocksProxyManager { credentials, self.version, command, + self.embed_session_info, )?))) } } impl SocksProxyManager { - pub(crate) fn new(server: SocketAddr, version: Version, credentials: Option) -> Self { + pub(crate) fn new(server: SocketAddr, version: Version, credentials: Option, embed_session_info: bool) -> Self { Self { server, credentials, version, + embed_session_info, } } }