Skip to content

Commit 2770521

Browse files
committed
Android implementation
1 parent 9441e52 commit 2770521

File tree

6 files changed

+180
-1
lines changed

6 files changed

+180
-1
lines changed

.github/workflows/rust.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,25 @@ jobs:
2626
run: cargo clippy --all-targets --all-features -- -D warnings
2727
- name: Build
2828
run: cargo build --verbose --tests --all-features
29+
30+
build_android:
31+
strategy:
32+
fail-fast: false
33+
runs-on: ubuntu-latest
34+
35+
steps:
36+
- uses: actions/checkout@v4
37+
- name: Install cargo ndk and rust compiler for android target
38+
if: ${{ !cancelled() }}
39+
run: |
40+
cargo install --locked cargo-ndk
41+
rustup target add x86_64-linux-android
42+
- name: clippy
43+
if: ${{ !cancelled() }}
44+
run: cargo ndk -t x86_64 clippy --all-features -- -D warnings
45+
- name: Build
46+
if: ${{ !cancelled() }}
47+
run: cargo ndk -t x86_64 rustc --verbose --all-features --lib --crate-type=cdylib
48+
- name: Abort on error
49+
if: ${{ failure() }}
50+
run: echo "Android build job failed" && false

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ rand = "0.8"
2424
socks5-impl = "0.5"
2525
tokio = { version = "1", features = ["full"] }
2626
tokio-util = "0.7"
27+
28+
[target.'cfg(target_os="android")'.dependencies]
29+
android_logger = "0.14"
30+
jni = { version = "0.21", default-features = false }

cbindgen.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ language = "C"
22
cpp_compat = true
33

44
[export]
5-
include = ["dns2socks_start", "dns2socks_stop", "dns2socks_set_log_callback"]
5+
include = [
6+
"dns2socks_start",
7+
"dns2socks_stop",
8+
"dns2socks_set_log_callback",
9+
"Java_com_github_shadowsocks_bg_Dns2socks_start",
10+
"Java_com_github_shadowsocks_bg_Dns2socks_stop",
11+
]
612
exclude = []
713

814
[export.rename]

src/android.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#![cfg(target_os = "android")]
2+
3+
use crate::{main_entry, ArgVerbosity, Config};
4+
use jni::{
5+
objects::{JClass, JString},
6+
sys::{jboolean, jint},
7+
JNIEnv,
8+
};
9+
10+
static TUN_QUIT: std::sync::Mutex<Option<tokio_util::sync::CancellationToken>> = std::sync::Mutex::new(None);
11+
12+
/// # Safety
13+
///
14+
/// Start dns2socks
15+
/// Parameters:
16+
/// - listen_addr: the listen address, e.g. "172.19.0.1:53", or null to use the default value
17+
/// - dns_remote_server: the dns remote server, e.g. "8.8.8.8:53", or null to use the default value
18+
/// - socks5_server: the socks5 server, e.g. "127.0.0.1:1080", or null to use the default value
19+
/// - username: the username for socks5 authentication, or null to use the default value
20+
/// - password: the password for socks5 authentication, or null to use the default value
21+
/// - force_tcp: whether to force tcp, true or false, default is false
22+
/// - cache_records: whether to cache dns records, true or false, default is false
23+
/// - verbosity: the verbosity level, see ArgVerbosity enum, default is ArgVerbosity::Info
24+
/// - timeout: the timeout in seconds, default is 5
25+
#[no_mangle]
26+
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Dns2socks_start(
27+
mut env: JNIEnv,
28+
_clazz: JClass,
29+
listen_addr: JString,
30+
dns_remote_server: JString,
31+
socks5_server: JString,
32+
username: JString,
33+
password: JString,
34+
force_tcp: jboolean,
35+
cache_records: jboolean,
36+
verbosity: jint,
37+
timeout: jint,
38+
) -> jint {
39+
let verbosity: ArgVerbosity = verbosity.try_into().unwrap_or_default();
40+
let filter_str = &format!("off,dns2socks={verbosity}");
41+
let filter = android_logger::FilterBuilder::new().parse(filter_str).build();
42+
android_logger::init_once(
43+
android_logger::Config::default()
44+
.with_tag("dns2socks")
45+
.with_max_level(log::LevelFilter::Trace)
46+
.with_filter(filter),
47+
);
48+
49+
let listen_addr = match get_java_string(&mut env, &listen_addr) {
50+
Ok(addr) => addr,
51+
Err(_e) => "0.0.0.0:53".to_string(),
52+
};
53+
let dns_remote_server = match get_java_string(&mut env, &dns_remote_server) {
54+
Ok(addr) => addr,
55+
Err(_e) => "8.8.8.8:53".to_string(),
56+
};
57+
let socks5_server = match get_java_string(&mut env, &socks5_server) {
58+
Ok(addr) => addr,
59+
Err(_e) => "127.0.0.1:1080".to_string(),
60+
};
61+
let username = match get_java_string(&mut env, &username) {
62+
Ok(addr) => Some(addr),
63+
Err(_e) => None,
64+
};
65+
let password = match get_java_string(&mut env, &password) {
66+
Ok(addr) => Some(addr),
67+
Err(_e) => None,
68+
};
69+
let force_tcp = force_tcp != 0;
70+
let cache_records = cache_records != 0;
71+
let timeout = if timeout < 3 { 5 } else { timeout as u64 };
72+
73+
let shutdown_token = tokio_util::sync::CancellationToken::new();
74+
if let Ok(mut lock) = TUN_QUIT.lock() {
75+
if lock.is_some() {
76+
return -1;
77+
}
78+
*lock = Some(shutdown_token.clone());
79+
} else {
80+
return -2;
81+
}
82+
83+
let main_loop = async move {
84+
let mut cfg = Config::default();
85+
cfg.verbosity(verbosity)
86+
.timeout(timeout)
87+
.force_tcp(force_tcp)
88+
.cache_records(cache_records)
89+
.listen_addr(listen_addr.parse()?)
90+
.dns_remote_server(dns_remote_server.parse()?)
91+
.socks5_server(socks5_server.parse()?)
92+
.username(username)
93+
.password(password);
94+
95+
if let Err(err) = main_entry(cfg, shutdown_token).await {
96+
log::error!("main loop error: {}", err);
97+
return Err(err);
98+
}
99+
Ok(())
100+
};
101+
102+
let exit_code = match tokio::runtime::Builder::new_multi_thread().enable_all().build() {
103+
Err(_e) => -3,
104+
Ok(rt) => match rt.block_on(main_loop) {
105+
Ok(_) => 0,
106+
Err(_e) => -4,
107+
},
108+
};
109+
110+
exit_code
111+
}
112+
113+
/// # Safety
114+
///
115+
/// Shutdown dns2socks
116+
#[no_mangle]
117+
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Dns2socks_stop(_env: JNIEnv, _: JClass) -> jint {
118+
if let Ok(mut lock) = TUN_QUIT.lock() {
119+
if let Some(shutdown_token) = lock.take() {
120+
shutdown_token.cancel();
121+
return 0;
122+
}
123+
}
124+
-1
125+
}
126+
127+
fn get_java_string(env: &mut JNIEnv, string: &JString) -> std::io::Result<String> {
128+
use std::io::{Error, ErrorKind::Other};
129+
Ok(env.get_string(string).map_err(|e| Error::new(Other, e))?.into())
130+
}

src/config.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,19 @@ impl From<ArgVerbosity> for log::LevelFilter {
157157
}
158158
}
159159
}
160+
161+
impl TryFrom<i32> for ArgVerbosity {
162+
type Error = std::io::Error;
163+
164+
fn try_from(value: i32) -> Result<Self, <ArgVerbosity as TryFrom<i32>>::Error> {
165+
match value {
166+
0 => Ok(ArgVerbosity::Off),
167+
1 => Ok(ArgVerbosity::Error),
168+
2 => Ok(ArgVerbosity::Warn),
169+
3 => Ok(ArgVerbosity::Info),
170+
4 => Ok(ArgVerbosity::Debug),
171+
5 => Ok(ArgVerbosity::Trace),
172+
_ => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid verbosity level")),
173+
}
174+
}
175+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod android;
12
mod api;
23
mod config;
34
mod dns;

0 commit comments

Comments
 (0)