Browse Source

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
pull/245/head
Alexander Olkhovoy 2 months ago
parent
commit
2f1e89e738
  1. 52
      src/android.rs
  2. 12
      src/args.rs
  3. 5
      src/lib.rs
  4. 22
      src/socks.rs

52
src/android.rs

@ -58,6 +58,58 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run(
.resolve::<jni::errors::LogErrorAndDefault>()
}
/// # Safety
///
/// Running tun2proxy with some arguments including embed_session_info option
/// Parameters:
/// - proxy_url: the proxy url, e.g. "socks5://user:[email protected]: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<jint> {
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::<jint, Error>(v)
})
.resolve::<jni::errors::LogErrorAndDefault>()
}
/// # Safety
///
/// Shutdown tun2proxy

12
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)]

5
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<dyn ProxyHandlerManager> = 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()),
};

22
src/socks.rs

@ -32,6 +32,7 @@ struct SocksProxyImpl {
credentials: Option<UserKey>,
command: protocol::Command,
udp_associate: Option<SocketAddr>,
embed_session_info: bool,
}
impl SocksProxyImpl {
@ -42,6 +43,7 @@ impl SocksProxyImpl {
credentials: Option<UserKey>,
version: Version,
command: protocol::Command,
embed_session_info: bool,
) -> Result<Self> {
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<UserKey>,
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<UserKey>) -> Self {
pub(crate) fn new(server: SocketAddr, version: Version, credentials: Option<UserKey>, embed_session_info: bool) -> Self {
Self {
server,
credentials,
version,
embed_session_info,
}
}
}

Loading…
Cancel
Save