Skip to content

Commit

Permalink
Android implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ssrlive committed Nov 22, 2024
1 parent 9441e52 commit 2770521
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 1 deletion.
22 changes: 22 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,25 @@ jobs:
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build
run: cargo build --verbose --tests --all-features

build_android:
strategy:
fail-fast: false
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Install cargo ndk and rust compiler for android target
if: ${{ !cancelled() }}
run: |
cargo install --locked cargo-ndk
rustup target add x86_64-linux-android
- name: clippy
if: ${{ !cancelled() }}
run: cargo ndk -t x86_64 clippy --all-features -- -D warnings
- name: Build
if: ${{ !cancelled() }}
run: cargo ndk -t x86_64 rustc --verbose --all-features --lib --crate-type=cdylib
- name: Abort on error
if: ${{ failure() }}
run: echo "Android build job failed" && false
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ rand = "0.8"
socks5-impl = "0.5"
tokio = { version = "1", features = ["full"] }
tokio-util = "0.7"

[target.'cfg(target_os="android")'.dependencies]
android_logger = "0.14"
jni = { version = "0.21", default-features = false }
8 changes: 7 additions & 1 deletion cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ language = "C"
cpp_compat = true

[export]
include = ["dns2socks_start", "dns2socks_stop", "dns2socks_set_log_callback"]
include = [
"dns2socks_start",
"dns2socks_stop",
"dns2socks_set_log_callback",
"Java_com_github_shadowsocks_bg_Dns2socks_start",
"Java_com_github_shadowsocks_bg_Dns2socks_stop",
]
exclude = []

[export.rename]
Expand Down
130 changes: 130 additions & 0 deletions src/android.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#![cfg(target_os = "android")]

use crate::{main_entry, ArgVerbosity, Config};
use jni::{
objects::{JClass, JString},
sys::{jboolean, jint},
JNIEnv,
};

static TUN_QUIT: std::sync::Mutex<Option<tokio_util::sync::CancellationToken>> = std::sync::Mutex::new(None);

/// # Safety
///
/// Start dns2socks
/// Parameters:
/// - listen_addr: the listen address, e.g. "172.19.0.1:53", or null to use the default value
/// - dns_remote_server: the dns remote server, e.g. "8.8.8.8:53", or null to use the default value
/// - socks5_server: the socks5 server, e.g. "127.0.0.1:1080", or null to use the default value
/// - username: the username for socks5 authentication, or null to use the default value
/// - password: the password for socks5 authentication, or null to use the default value
/// - force_tcp: whether to force tcp, true or false, default is false
/// - cache_records: whether to cache dns records, true or false, default is false
/// - verbosity: the verbosity level, see ArgVerbosity enum, default is ArgVerbosity::Info
/// - timeout: the timeout in seconds, default is 5
#[no_mangle]
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Dns2socks_start(
mut env: JNIEnv,
_clazz: JClass,
listen_addr: JString,
dns_remote_server: JString,
socks5_server: JString,
username: JString,
password: JString,
force_tcp: jboolean,
cache_records: jboolean,
verbosity: jint,
timeout: jint,
) -> jint {
let verbosity: ArgVerbosity = verbosity.try_into().unwrap_or_default();
let filter_str = &format!("off,dns2socks={verbosity}");
let filter = android_logger::FilterBuilder::new().parse(filter_str).build();
android_logger::init_once(
android_logger::Config::default()
.with_tag("dns2socks")
.with_max_level(log::LevelFilter::Trace)
.with_filter(filter),
);

let listen_addr = match get_java_string(&mut env, &listen_addr) {
Ok(addr) => addr,
Err(_e) => "0.0.0.0:53".to_string(),
};
let dns_remote_server = match get_java_string(&mut env, &dns_remote_server) {
Ok(addr) => addr,
Err(_e) => "8.8.8.8:53".to_string(),
};
let socks5_server = match get_java_string(&mut env, &socks5_server) {
Ok(addr) => addr,
Err(_e) => "127.0.0.1:1080".to_string(),
};
let username = match get_java_string(&mut env, &username) {
Ok(addr) => Some(addr),
Err(_e) => None,
};
let password = match get_java_string(&mut env, &password) {
Ok(addr) => Some(addr),
Err(_e) => None,
};
let force_tcp = force_tcp != 0;
let cache_records = cache_records != 0;
let timeout = if timeout < 3 { 5 } else { timeout as u64 };

let shutdown_token = tokio_util::sync::CancellationToken::new();
if let Ok(mut lock) = TUN_QUIT.lock() {
if lock.is_some() {
return -1;
}
*lock = Some(shutdown_token.clone());
} else {
return -2;
}

let main_loop = async move {
let mut cfg = Config::default();
cfg.verbosity(verbosity)
.timeout(timeout)
.force_tcp(force_tcp)
.cache_records(cache_records)
.listen_addr(listen_addr.parse()?)
.dns_remote_server(dns_remote_server.parse()?)
.socks5_server(socks5_server.parse()?)
.username(username)
.password(password);

if let Err(err) = main_entry(cfg, shutdown_token).await {
log::error!("main loop error: {}", err);
return Err(err);
}
Ok(())
};

let exit_code = match tokio::runtime::Builder::new_multi_thread().enable_all().build() {
Err(_e) => -3,
Ok(rt) => match rt.block_on(main_loop) {
Ok(_) => 0,
Err(_e) => -4,
},
};

exit_code
}

/// # Safety
///
/// Shutdown dns2socks
#[no_mangle]
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Dns2socks_stop(_env: JNIEnv, _: JClass) -> jint {
if let Ok(mut lock) = TUN_QUIT.lock() {
if let Some(shutdown_token) = lock.take() {
shutdown_token.cancel();
return 0;
}
}
-1
}

fn get_java_string(env: &mut JNIEnv, string: &JString) -> std::io::Result<String> {
use std::io::{Error, ErrorKind::Other};
Ok(env.get_string(string).map_err(|e| Error::new(Other, e))?.into())
}
16 changes: 16 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,19 @@ impl From<ArgVerbosity> for log::LevelFilter {
}
}
}

impl TryFrom<i32> for ArgVerbosity {
type Error = std::io::Error;

fn try_from(value: i32) -> Result<Self, <ArgVerbosity as TryFrom<i32>>::Error> {
match value {
0 => Ok(ArgVerbosity::Off),
1 => Ok(ArgVerbosity::Error),
2 => Ok(ArgVerbosity::Warn),
3 => Ok(ArgVerbosity::Info),
4 => Ok(ArgVerbosity::Debug),
5 => Ok(ArgVerbosity::Trace),
_ => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid verbosity level")),
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod android;
mod api;
mod config;
mod dns;
Expand Down

0 comments on commit 2770521

Please sign in to comment.