feat(android): add JNI build and VPN support
This commit is contained in:
+102
-2
@@ -86,7 +86,6 @@ jobs:
|
||||
NAME: Vnt
|
||||
TARGET: ${{ matrix.TARGET }}
|
||||
OS: ${{ matrix.OS }}
|
||||
FEATURES: ${{ matrix.FEATURES }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Replace Web static files with current frontend build
|
||||
@@ -295,10 +294,103 @@ jobs:
|
||||
name: Vnt-${{ matrix.TARGET }}
|
||||
path: |
|
||||
./artifacts/*
|
||||
|
||||
build-android-jni:
|
||||
name: Build Android JNI (${{ matrix.abi }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-android
|
||||
abi: arm64-v8a
|
||||
clang: aarch64-linux-android24-clang
|
||||
cargo_env: AARCH64_LINUX_ANDROID
|
||||
cc_env: CC_aarch64_linux_android
|
||||
ar_env: AR_aarch64_linux_android
|
||||
- target: armv7-linux-androideabi
|
||||
abi: armeabi-v7a
|
||||
clang: armv7a-linux-androideabi24-clang
|
||||
cargo_env: ARMV7_LINUX_ANDROIDEABI
|
||||
cc_env: CC_armv7_linux_androideabi
|
||||
ar_env: AR_armv7_linux_androideabi
|
||||
- target: x86_64-linux-android
|
||||
abi: x86_64
|
||||
clang: x86_64-linux-android24-clang
|
||||
cargo_env: X86_64_LINUX_ANDROID
|
||||
cc_env: CC_x86_64_linux_android
|
||||
ar_env: AR_x86_64_linux_android
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust target
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Set up Android SDK
|
||||
uses: android-actions/setup-android@v4
|
||||
|
||||
- name: Install Android NDK r27d
|
||||
run: sdkmanager "ndk;27.3.13750724"
|
||||
|
||||
- name: Cache Cargo dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: android-jni-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
|
||||
restore-keys: android-jni-${{ matrix.target }}-
|
||||
|
||||
- name: Build vnt-jni
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
CLANG: ${{ matrix.clang }}
|
||||
CARGO_ENV: ${{ matrix.cargo_env }}
|
||||
CC_ENV: ${{ matrix.cc_env }}
|
||||
AR_ENV: ${{ matrix.ar_env }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NDK_HOME="$ANDROID_SDK_ROOT/ndk/27.3.13750724"
|
||||
toolchain="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
export PATH="$toolchain:$PATH"
|
||||
export "CARGO_TARGET_${CARGO_ENV}_LINKER=$toolchain/$CLANG"
|
||||
export "CARGO_TARGET_${CARGO_ENV}_RUSTFLAGS=-C link-arg=-Wl,-z,max-page-size=16384"
|
||||
export "$CC_ENV=$toolchain/$CLANG"
|
||||
export "$AR_ENV=$toolchain/llvm-ar"
|
||||
cargo build --locked --release -p vnt-jni --target "$TARGET"
|
||||
|
||||
- name: Verify and package native library
|
||||
env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
ABI: ${{ matrix.abi }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NDK_HOME="$ANDROID_SDK_ROOT/ndk/27.3.13750724"
|
||||
library="target/$TARGET/release/libvnt_jni.so"
|
||||
readelf="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf"
|
||||
nm="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm"
|
||||
test -s "$library"
|
||||
"$nm" -D --defined-only "$library" | grep -q Java_com_vnt_VntManager_nativeInit
|
||||
"$readelf" -l "$library" | awk '/ LOAD / { found=1; if ($NF != "0x4000") bad=1 } END { exit (!found || bad) }'
|
||||
mkdir -p artifacts
|
||||
asset="artifacts/vnt-jni-${ABI}-${GITHUB_REF_NAME}.so"
|
||||
cp "$library" "$asset"
|
||||
(cd artifacts && sha256sum "$(basename "$asset")" > "$(basename "$asset").sha256")
|
||||
|
||||
- name: Upload Android JNI artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Vnt-Android-${{ matrix.abi }}
|
||||
path: artifacts/vnt-jni-${{ matrix.abi }}-${{ github.ref_name }}.so*
|
||||
if-no-files-found: error
|
||||
# deploys to github releases on tag
|
||||
deploy:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
needs: build
|
||||
needs: [build, build-android-jni]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
@@ -315,6 +407,14 @@ jobs:
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
- name: Release Android JNI libraries
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ./artifacts/Vnt-Android-*/*.so*
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
|
||||
desktop-windows:
|
||||
name: Build and publish VNT Desktop for Windows
|
||||
|
||||
Generated
+3
-3
@@ -2805,9 +2805,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "machine-uid"
|
||||
version = "0.5.4"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d7217d573cdb141d6da43113b098172e057d39915d79c4bdedbc3aacd46bd96"
|
||||
checksum = "cfe0d6336d341b10ae80e099b8f3c59f34bf8aef66ef25e83aa98bf2955c0ae7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-registry",
|
||||
@@ -6351,7 +6351,7 @@ dependencies = [
|
||||
"uuid",
|
||||
"widestring",
|
||||
"winapi",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
|
||||
+4
-2
@@ -44,7 +44,6 @@ reed-solomon-erasure = "6.0"
|
||||
|
||||
serde.workspace = true
|
||||
rcgen = "0.14.6"
|
||||
machine-uid = "0.5.4"
|
||||
|
||||
zerocopy = { version = "0.8.31", features = ["derive"] }
|
||||
socket2 = { version = "0.6.1", features = ["all"] }
|
||||
@@ -52,11 +51,14 @@ socket2 = { version = "0.6.1", features = ["all"] }
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["winreg"] }
|
||||
widestring = "1.2"
|
||||
windows-sys = { version = "0.60.2", features = ["Win32_Networking_WinSock"] }
|
||||
windows-sys = { version = "0.61", features = ["Win32_Networking_WinSock"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
machine-uid = "0.6"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.14"
|
||||
protoc-bin-vendored = "3"
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::tunnel_core::server::rpc::ServerRPC;
|
||||
use crate::utils::task_control::TaskGroup;
|
||||
use anyhow::bail;
|
||||
use ipnet::Ipv4Net;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
pub const DEFAULT_MTU: u16 = 1380;
|
||||
|
||||
@@ -10,7 +10,9 @@ use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tun_rs::async_framed::{Decoder, DeviceFramedRead, DeviceFramedWrite, Encoder};
|
||||
use tun_rs::{AsyncDevice, DeviceBuilder};
|
||||
use tun_rs::AsyncDevice;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use tun_rs::DeviceBuilder;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceIOManager {
|
||||
@@ -19,6 +21,7 @@ pub struct DeviceIOManager {
|
||||
}
|
||||
type DeviceMutex = Arc<tokio::sync::Mutex<(Option<DeviceTask>, Option<(Ipv4Addr, u8)>)>>;
|
||||
pub struct DeviceTask {
|
||||
#[cfg_attr(target_os = "android", allow(dead_code))]
|
||||
device: Arc<AsyncDevice>,
|
||||
task: SubTask,
|
||||
}
|
||||
@@ -104,6 +107,7 @@ impl DeviceIOManager {
|
||||
bail!("device doesn't exist")
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub async fn set_network(&self, ip: Ipv4Addr, prefix_len: u8) -> anyhow::Result<()> {
|
||||
let mut guard = self.device.lock().await;
|
||||
let Some(dev) = guard.0.as_ref() else {
|
||||
@@ -124,6 +128,15 @@ impl DeviceIOManager {
|
||||
}
|
||||
|
||||
fn create_tun(config: DeviceConfig) -> anyhow::Result<AsyncDevice> {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
let fd = config.tun_fd.context("Android requires a VpnService TUN fd")?;
|
||||
// SAFETY: The fd comes directly from ParcelFileDescriptor returned by
|
||||
// VpnService.Builder.establish and remains open for the network lifetime.
|
||||
return unsafe { Ok(AsyncDevice::from_fd(fd)?) };
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
#[cfg(unix)]
|
||||
if let Some(fd) = config.tun_fd {
|
||||
// SAFETY: Caller must ensure fd is a valid, open file descriptor for a TUN device.
|
||||
@@ -151,6 +164,7 @@ fn create_tun(config: DeviceConfig) -> anyhow::Result<AsyncDevice> {
|
||||
_ = dev.set_tx_queue_len(1000);
|
||||
}
|
||||
Ok(dev)
|
||||
}
|
||||
}
|
||||
fn create(
|
||||
task_group: &TaskGroup,
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::Context;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
pub fn get_device_id() -> anyhow::Result<String> {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
match machine_uid::get() {
|
||||
Ok(id) => return Ok(id),
|
||||
Err(e) => {
|
||||
|
||||
+37
-40
@@ -300,54 +300,51 @@ pub extern "system" fn Java_com_vnt_VntNetwork_nativeSetNetworkIp<'local>(
|
||||
ip: JString<'local>,
|
||||
prefix_len: jint,
|
||||
) -> jboolean {
|
||||
jni_guard!(env, 0, {
|
||||
let result: anyhow::Result<()> = (|| {
|
||||
let (network_manager_arc, runtime) = {
|
||||
let mut global_state = GLOBAL_STATE.lock();
|
||||
let state = global_state.as_mut().context("VNT not initialized")?;
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
let _ = (handle, ip, prefix_len);
|
||||
let _ = env.throw("set_network_ip is not supported on Android");
|
||||
0
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
jni_guard!(env, 0, {
|
||||
let result: anyhow::Result<()> = (|| {
|
||||
let (network_manager_arc, runtime) = {
|
||||
let mut global_state = GLOBAL_STATE.lock();
|
||||
let state = global_state.as_mut().context("VNT not initialized")?;
|
||||
|
||||
let network_manager_arc = state
|
||||
.network_managers
|
||||
.get(&handle)
|
||||
.context("Invalid handle")?
|
||||
.clone();
|
||||
let network_manager_arc = state
|
||||
.network_managers
|
||||
.get(&handle)
|
||||
.context("Invalid handle")?
|
||||
.clone();
|
||||
|
||||
let runtime = state.runtime.clone();
|
||||
(network_manager_arc, runtime)
|
||||
};
|
||||
let runtime = state.runtime.clone();
|
||||
(network_manager_arc, runtime)
|
||||
};
|
||||
|
||||
let ip_str: String = env.get_string(&ip)?.into();
|
||||
let ip_addr: Ipv4Addr = ip_str.parse().context("Invalid IP address")?;
|
||||
|
||||
let manager_lock = network_manager_arc.lock();
|
||||
let manager = manager_lock
|
||||
.as_ref()
|
||||
.context("Network manager already destroyed")?;
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
let ip_str: String = env.get_string(&ip)?.into();
|
||||
let ip_addr: Ipv4Addr = ip_str.parse().context("Invalid IP address")?;
|
||||
let manager_lock = network_manager_arc.lock();
|
||||
let manager = manager_lock
|
||||
.as_ref()
|
||||
.context("Network manager already destroyed")?;
|
||||
runtime.block_on(async {
|
||||
manager.set_tun_network_ip(ip_addr, prefix_len as u8).await
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
let _ = (ip_addr, prefix_len); // 避免未使用警告
|
||||
anyhow::bail!("set_network_ip is not supported on Android");
|
||||
match result {
|
||||
Ok(_) => 1,
|
||||
Err(e) => {
|
||||
let _ = env.throw(format!("Failed to set network IP: {:?}", e));
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
match result {
|
||||
Ok(_) => 1,
|
||||
Err(e) => {
|
||||
let _ = env.throw(format!("Failed to set network IP: {:?}", e));
|
||||
0
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取VntApi实例
|
||||
|
||||
Reference in New Issue
Block a user