diff --git a/mobile/Cargo.lock b/mobile/Cargo.lock index 212a5ab2..988e3c66 100644 --- a/mobile/Cargo.lock +++ b/mobile/Cargo.lock @@ -301,6 +301,15 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -431,9 +440,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -614,16 +623,16 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.10", + "parking_lot_core 0.9.12", ] [[package]] @@ -727,7 +736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -917,7 +926,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.1.3", + "windows-link 0.2.1", "windows-result", ] @@ -1688,11 +1697,10 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1825,7 +1833,7 @@ dependencies = [ [[package]] name = "mycelium" -version = "0.7.7" +version = "0.7.9" dependencies = [ "aes-gcm", "ahash 0.8.11", @@ -1849,7 +1857,7 @@ dependencies = [ "libc", "mycelium-tun", "netdev", - "nix 0.31.2", + "nix 0.31.3", "openssl", "quinn", "rand 0.10.1", @@ -1874,7 +1882,7 @@ dependencies = [ [[package]] name = "mycelium-api" -version = "0.7.7" +version = "0.7.9" dependencies = [ "async-trait", "axum", @@ -1890,7 +1898,7 @@ dependencies = [ [[package]] name = "mycelium-metrics" -version = "0.7.7" +version = "0.7.9" dependencies = [ "axum", "mycelium", @@ -1904,7 +1912,7 @@ name = "mycelium-tun" version = "0.1.0" dependencies = [ "libc", - "nix 0.31.2", + "nix 0.31.3", "tokio", "tracing", ] @@ -2013,9 +2021,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags 2.9.0", "cfg-if", @@ -2178,9 +2186,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "critical-section", "portable-atomic", @@ -2194,9 +2202,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.79" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags 2.9.0", "cfg-if", @@ -2228,9 +2236,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -2263,7 +2271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", + "parking_lot_core 0.9.12", ] [[package]] @@ -2282,15 +2290,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall 0.5.11", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -2501,7 +2509,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.5.9", + "socket2 0.6.3", "thiserror 2.0.12", "tokio", "tracing", @@ -2642,9 +2650,9 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rcgen" -version = "0.14.7" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" dependencies = [ "pem", "ring", @@ -2877,7 +2885,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3241,10 +3249,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3355,9 +3363,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.2" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3750,7 +3758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba782755fc073877e567c2253c0be48e4aa9a254c232d36d3985dfae0bd5205" dependencies = [ "libc", - "nix 0.31.2", + "nix 0.31.3", ] [[package]] @@ -3967,7 +3975,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4385,10 +4393,11 @@ checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yasna" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" dependencies = [ + "bit-vec", "time", ] diff --git a/mobile/Cargo.toml b/mobile/Cargo.toml index 019cbe01..488a0b51 100644 --- a/mobile/Cargo.toml +++ b/mobile/Cargo.toml @@ -19,6 +19,9 @@ once_cell = "1.21.1" serde_json = "1.0" [target.'cfg(target_os = "android")'.dependencies] +# Android consumers (VpnService.Builder hands in a pre-configured fd) use the +# fd-handoff TUN path. This feature enables that path in mycelium. +mycelium = { path = "../mycelium", features = ["vendored-openssl", "androidtunfd"] } tracing-android = "0.2.0" [target.'cfg(target_os = "ios")'.dependencies] diff --git a/mycelium-tun/Cargo.toml b/mycelium-tun/Cargo.toml index 3f168dba..9c2df380 100644 --- a/mycelium-tun/Cargo.toml +++ b/mycelium-tun/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" license-file = "../LICENSE" -[target.'cfg(target_os = "linux")'.dependencies] +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] tokio = { version = "1.52.3", default-features = false, features = ["net"] } nix = { version = "0.31.3", features = ["ioctl", "net"] } libc = "0.2" diff --git a/mycelium-tun/src/lib.rs b/mycelium-tun/src/lib.rs index f10cda64..45916917 100644 --- a/mycelium-tun/src/lib.rs +++ b/mycelium-tun/src/lib.rs @@ -1,13 +1,15 @@ //! Platform-specific TUN device implementations. //! -//! Currently only Linux is supported. On other platforms this crate is empty. +//! Linux and Android share the same implementation (Android is Linux + bionic +//! and exposes the same `/dev/net/tun` uAPI). On other platforms this crate +//! is empty. -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] mod checksum; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] mod linux; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] mod offload; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] pub use linux::{ReadHalf, Tun, WriteHalf}; diff --git a/mycelium/Cargo.toml b/mycelium/Cargo.toml index 5b7bd9d7..1eb05279 100644 --- a/mycelium/Cargo.toml +++ b/mycelium/Cargo.toml @@ -12,6 +12,11 @@ vendored-openssl = ["openssl/vendored"] mactunfd = [ "tun/appstore", ] #mactunfd is a flag to specify that macos should provide tun FD instead of tun name +# androidtunfd is the Android equivalent of mactunfd: when enabled the caller +# (typically an app using Android's VpnService) provides a pre-configured TUN +# file descriptor. Default Android builds (e.g. a `cargo ndk` system-service +# build) open and manage the TUN device themselves via the shared Linux path. +androidtunfd = [] # Build a C ABI surface (cdylib + staticlib + generated header) on top of the # core node. Used by external daemons that wrap libmycelium without depending # on Rust as a build dependency. @@ -97,6 +102,8 @@ wintun = "0.5.1" [target.'cfg(target_os = "android")'.dependencies] tun = { git = "https://github.com/LeeSmet/rust-tun", features = ["async"] } libc = "0.2.186" +nix = { version = "0.31.3", features = ["socket"] } +mycelium-tun = { path = "../mycelium-tun" } [target.'cfg(target_os = "ios")'.dependencies] tun = { git = "https://github.com/LeeSmet/rust-tun", features = ["async"] } diff --git a/mycelium/src/ffi/exports.rs b/mycelium/src/ffi/exports.rs index 485dbce5..030e755c 100644 --- a/mycelium/src/ffi/exports.rs +++ b/mycelium/src/ffi/exports.rs @@ -169,7 +169,11 @@ pub unsafe extern "C" fn mycelium_start( Err(_) => return std::ptr::null_mut(), }; - #[cfg(not(any(target_os = "ios", all(target_os = "macos", feature = "mactunfd"))))] + #[cfg(not(any( + target_os = "ios", + all(target_os = "macos", feature = "mactunfd"), + all(target_os = "android", feature = "androidtunfd"), + )))] let tun_name = match cstr_to_str(cfg.tun_name, "tun_name") { Ok(s) => s.to_owned(), Err(_) => return std::ptr::null_mut(), @@ -177,16 +181,11 @@ pub unsafe extern "C" fn mycelium_start( info!(peers = peers_strs.len(), "starting mycelium node"); - #[cfg(target_os = "android")] - let tun_fd = match crate::node_handle::create_tun_fd(&tun_name) { - Ok(fd) => fd, - Err(e) => { - error!("Failed to create TUN fd: {e}"); - error::set(format!("failed to create tun fd: {e}")); - return std::ptr::null_mut(); - } - }; - #[cfg(any(target_os = "ios", all(target_os = "macos", feature = "mactunfd")))] + #[cfg(any( + target_os = "ios", + all(target_os = "macos", feature = "mactunfd"), + all(target_os = "android", feature = "androidtunfd"), + ))] let tun_fd = cfg.tun_fd; let config = Config { @@ -196,13 +195,14 @@ pub unsafe extern "C" fn mycelium_start( #[cfg(any( target_os = "linux", all(target_os = "macos", not(feature = "mactunfd")), - target_os = "windows" + target_os = "windows", + all(target_os = "android", not(feature = "androidtunfd")), ))] tun_name, #[cfg(any( - target_os = "android", target_os = "ios", all(target_os = "macos", feature = "mactunfd"), + all(target_os = "android", feature = "androidtunfd"), ))] tun_fd: Some(tun_fd), tcp_listen_port: cfg.tcp_listen_port, diff --git a/mycelium/src/lib.rs b/mycelium/src/lib.rs index 661d02f1..38857f12 100644 --- a/mycelium/src/lib.rs +++ b/mycelium/src/lib.rs @@ -84,7 +84,8 @@ pub struct Config { #[cfg(any( target_os = "linux", all(target_os = "macos", not(feature = "mactunfd")), - target_os = "windows" + target_os = "windows", + all(target_os = "android", not(feature = "androidtunfd")), ))] pub tun_name: String, @@ -97,14 +98,16 @@ pub struct Config { /// Mark that's set on all packets that we send on the underlying network pub firewall_mark: Option, - // tun_fd is android, iOS, macos on appstore specific option - // We can't create TUN device from the Rust code in android, iOS, and macos on appstore. - // So, we create the TUN device on Kotlin(android) or Swift(iOS, macos) then pass - // the TUN's file descriptor to mycelium. + // tun_fd is the iOS / macos-appstore / android-VpnService option. + // We can't create the TUN device from Rust on iOS or macOS-appstore (the + // platform doesn't expose `/dev/net/tun` to the app). Android apps using + // `VpnService.Builder` are in the same situation — the framework hands + // back a ready fd. In these cases the TUN is created by Kotlin (android) + // or Swift (iOS, macOS) and the file descriptor is passed to mycelium. #[cfg(any( - target_os = "android", target_os = "ios", all(target_os = "macos", feature = "mactunfd"), + all(target_os = "android", feature = "androidtunfd"), ))] pub tun_fd: Option, @@ -281,7 +284,8 @@ where #[cfg(any( target_os = "linux", all(target_os = "macos", not(feature = "mactunfd")), - target_os = "windows" + target_os = "windows", + all(target_os = "android", not(feature = "androidtunfd")), ))] let tun_config = TunConfig { name: config.tun_name.clone(), @@ -291,9 +295,9 @@ where .expect("Static configured TUN route is valid; qed"), }; #[cfg(any( - target_os = "android", target_os = "ios", all(target_os = "macos", feature = "mactunfd"), + all(target_os = "android", feature = "androidtunfd"), ))] let tun_config = TunConfig { tun_fd: config.tun_fd.unwrap(), diff --git a/mycelium/src/node_handle.rs b/mycelium/src/node_handle.rs index 87907059..68a41535 100644 --- a/mycelium/src/node_handle.rs +++ b/mycelium/src/node_handle.rs @@ -36,40 +36,6 @@ impl std::fmt::Display for NodeError { impl std::error::Error for NodeError {} -// ── TUN setup (Android only) ──────────────────────────────────────────────── - -/// On Android the `tun` crate expects an already-opened file descriptor. -/// This function opens `/dev/tun`, configures the interface with TUNSETIFF, -/// and returns the raw fd. Requires `CAP_NET_ADMIN`. -#[cfg(target_os = "android")] -pub fn create_tun_fd(tun_name: &str) -> Result { - const TUNSETIFF: libc::c_ulong = 0x400454ca; - const IFF_TUN: libc::c_short = 0x0001; - const IFF_NO_PI: libc::c_short = 0x1000; - - let fd = unsafe { libc::open(b"/dev/tun\0".as_ptr() as *const libc::c_char, libc::O_RDWR) }; - if fd < 0 { - return Err(std::io::Error::last_os_error()); - } - - let mut ifr = [0u8; 40]; - let name_bytes = tun_name.as_bytes(); - let len = name_bytes.len().min(15); - ifr[..len].copy_from_slice(&name_bytes[..len]); - let flags: i16 = IFF_TUN | IFF_NO_PI; - ifr[16] = (flags & 0xff) as u8; - ifr[17] = ((flags >> 8) & 0xff) as u8; - - let ret = unsafe { libc::ioctl(fd, TUNSETIFF as i32, ifr.as_ptr()) }; - if ret < 0 { - let err = std::io::Error::last_os_error(); - unsafe { libc::close(fd) }; - return Err(err); - } - - Ok(fd) -} - // ── NodeHandle ────────────────────────────────────────────────────────────── /// Upper bound on how long node teardown waits for the background Tokio @@ -90,14 +56,6 @@ pub struct NodeHandle { /// teardown is synchronous: once the join completes the runtime is gone /// and any TUN interface the node created has been removed. thread: Option>, - /// On Android the `tun` crate does not close the TUN file descriptor on - /// drop (it assumes the fd is owned by Android's `VpnService`). Mycelium - /// opens this fd itself via [`create_tun_fd`], so it owns it and must - /// close it during teardown — otherwise the kernel keeps the - /// non-persistent TUN interface alive. Closed in [`Drop`], after the - /// background runtime has been joined. - #[cfg(target_os = "android")] - tun_fd: Option, } impl NodeHandle { @@ -106,11 +64,6 @@ impl NodeHandle { /// Blocks the calling thread until the node is ready (or fails to start). /// The caller provides a fully constructed [`Config`]. pub fn start(config: Config) -> Result { - // On Android mycelium opens the TUN fd itself (see `create_tun_fd`), - // so the handle owns it and is responsible for closing it on drop. - #[cfg(target_os = "android")] - let tun_fd = config.tun_fd; - let (result_tx, result_rx) = std::sync::mpsc::sync_channel(1); let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>(); @@ -158,17 +111,8 @@ impl NodeHandle { Ok(Ok(pair)) => pair, other => { // The node failed to start. Wait for the background runtime - // to finish tearing down, then release the TUN fd we opened - // (on Android nothing else will close it). + // to finish tearing down before returning. let _ = thread.join(); - #[cfg(target_os = "android")] - if let Some(fd) = tun_fd { - if fd >= 0 { - // SAFETY: `fd` came from `create_tun_fd`; the runtime - // that used it has been joined, so it is unreferenced. - unsafe { libc::close(fd) }; - } - } return Err(match other { Ok(Err(e)) => e, _ => NodeError::ThreadPanic, @@ -181,8 +125,6 @@ impl NodeHandle { rt_handle, shutdown_tx: Some(shutdown_tx), thread: Some(thread), - #[cfg(target_os = "android")] - tun_fd, }) } @@ -233,20 +175,6 @@ impl Drop for NodeHandle { error!("node background thread panicked during shutdown: {e:?}"); } } - // On Android the `tun` crate does not close the TUN fd on drop (it - // assumes Android's `VpnService` owns it). Mycelium opened this fd - // itself via `create_tun_fd`, so it must close it here — after the - // join above guarantees the runtime, and the tun device using the - // fd, are gone. The interface is non-persistent, so closing the last - // fd makes the kernel remove it. - #[cfg(target_os = "android")] - if let Some(fd) = self.tun_fd.take() { - if fd >= 0 { - // SAFETY: `fd` came from `create_tun_fd`; the runtime that - // used it has been joined, so nothing else references it. - unsafe { libc::close(fd) }; - } - } } } diff --git a/mycelium/src/tun.rs b/mycelium/src/tun.rs index c4831f2c..5ccdbd1d 100644 --- a/mycelium/src/tun.rs +++ b/mycelium/src/tun.rs @@ -3,14 +3,16 @@ #[cfg(any( target_os = "linux", all(target_os = "macos", not(feature = "mactunfd")), - target_os = "windows" + target_os = "windows", + all(target_os = "android", not(feature = "androidtunfd")), ))] use crate::subnet::Subnet; #[cfg(any( target_os = "linux", all(target_os = "macos", not(feature = "mactunfd")), - target_os = "windows" + target_os = "windows", + all(target_os = "android", not(feature = "androidtunfd")), ))] pub struct TunConfig { pub name: String, @@ -19,17 +21,26 @@ pub struct TunConfig { } #[cfg(any( - target_os = "android", target_os = "ios", all(target_os = "macos", feature = "mactunfd"), + all(target_os = "android", feature = "androidtunfd"), ))] pub struct TunConfig { pub tun_fd: i32, } -#[cfg(target_os = "linux")] + +// Android without the `androidtunfd` feature uses the same Linux uAPI as +// `target_os = "linux"`, so it compiles `tun/linux.rs`. +#[cfg(any( + target_os = "linux", + all(target_os = "android", not(feature = "androidtunfd")), +))] mod linux; -#[cfg(target_os = "linux")] +#[cfg(any( + target_os = "linux", + all(target_os = "android", not(feature = "androidtunfd")), +))] pub use linux::new; #[cfg(all(target_os = "macos", not(feature = "mactunfd")))] @@ -43,9 +54,9 @@ mod windows; #[cfg(target_os = "windows")] pub use windows::new; -#[cfg(target_os = "android")] +#[cfg(all(target_os = "android", feature = "androidtunfd"))] mod android; -#[cfg(target_os = "android")] +#[cfg(all(target_os = "android", feature = "androidtunfd"))] pub use android::new; #[cfg(any(target_os = "ios", all(target_os = "macos", feature = "mactunfd")))]