Skip to content

Commit 5fd4cf8

Browse files
committed
Make regular linux tun code available on android
Previously android always used the same code which was an issue for system services. Signed-off-by: Lee Smet <lee.smet@hotmail.com>
1 parent 703e7fd commit 5fd4cf8

8 files changed

Lines changed: 63 additions & 237 deletions

File tree

mobile/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ once_cell = "1.21.1"
1919
serde_json = "1.0"
2020

2121
[target.'cfg(target_os = "android")'.dependencies]
22+
# Android consumers (VpnService.Builder hands in a pre-configured fd) use the
23+
# fd-handoff TUN path. This feature enables that path in mycelium.
24+
mycelium = { path = "../mycelium", features = ["vendored-openssl", "androidtunfd"] }
2225
tracing-android = "0.2.0"
2326

2427
[target.'cfg(target_os = "ios")'.dependencies]

mycelium-tun/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "0.1.0"
44
edition = "2024"
55
license-file = "../LICENSE"
66

7-
[target.'cfg(target_os = "linux")'.dependencies]
7+
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
88
tokio = { version = "1.52.3", default-features = false, features = ["net"] }
99
nix = { version = "0.31.3", features = ["ioctl", "net"] }
1010
libc = "0.2"

mycelium-tun/src/lib.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
//! Platform-specific TUN device implementations.
22
//!
3-
//! Currently only Linux is supported. On other platforms this crate is empty.
3+
//! Linux and Android share the same implementation (Android is Linux + bionic
4+
//! and exposes the same `/dev/net/tun` uAPI). On other platforms this crate
5+
//! is empty.
46
5-
#[cfg(target_os = "linux")]
7+
#[cfg(any(target_os = "linux", target_os = "android"))]
68
mod checksum;
7-
#[cfg(target_os = "linux")]
9+
#[cfg(any(target_os = "linux", target_os = "android"))]
810
mod linux;
9-
#[cfg(target_os = "linux")]
11+
#[cfg(any(target_os = "linux", target_os = "android"))]
1012
mod offload;
1113

12-
#[cfg(target_os = "linux")]
14+
#[cfg(any(target_os = "linux", target_os = "android"))]
1315
pub use linux::{ReadHalf, Tun, WriteHalf};

mycelium/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ vendored-openssl = ["openssl/vendored"]
1212
mactunfd = [
1313
"tun/appstore",
1414
] #mactunfd is a flag to specify that macos should provide tun FD instead of tun name
15+
# androidtunfd is the Android equivalent of mactunfd: when enabled the caller
16+
# (typically an app using Android's VpnService) provides a pre-configured TUN
17+
# file descriptor. Default Android builds (e.g. a `cargo ndk` system-service
18+
# build) open and manage the TUN device themselves via the shared Linux path.
19+
androidtunfd = []
1520
# Build a C ABI surface (cdylib + staticlib + generated header) on top of the
1621
# core node. Used by external daemons that wrap libmycelium without depending
1722
# on Rust as a build dependency.
@@ -97,6 +102,8 @@ wintun = "0.5.1"
97102
[target.'cfg(target_os = "android")'.dependencies]
98103
tun = { git = "https://github.com/LeeSmet/rust-tun", features = ["async"] }
99104
libc = "0.2.186"
105+
nix = { version = "0.31.3", features = ["socket"] }
106+
mycelium-tun = { path = "../mycelium-tun" }
100107

101108
[target.'cfg(target_os = "ios")'.dependencies]
102109
tun = { git = "https://github.com/LeeSmet/rust-tun", features = ["async"] }

mycelium/src/ffi/exports.rs

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -169,53 +169,40 @@ pub unsafe extern "C" fn mycelium_start(
169169
Err(_) => return std::ptr::null_mut(),
170170
};
171171

172-
#[cfg(not(any(target_os = "ios", all(target_os = "macos", feature = "mactunfd"))))]
172+
#[cfg(not(any(
173+
target_os = "ios",
174+
all(target_os = "macos", feature = "mactunfd"),
175+
all(target_os = "android", feature = "androidtunfd"),
176+
)))]
173177
let tun_name = match cstr_to_str(cfg.tun_name, "tun_name") {
174178
Ok(s) => s.to_owned(),
175179
Err(_) => return std::ptr::null_mut(),
176180
};
177181

178182
info!(peers = peers_strs.len(), "starting mycelium node");
179183

180-
let node_key = crypto::SecretKey::from(cfg.priv_key);
181-
182-
#[cfg(target_os = "android")]
183-
let tun_fd = {
184-
// MTU matches `LINK_MTU` in `mycelium/src/tun/android.rs`; the prefix
185-
// length matches what `tun/linux.rs` uses, so the kernel installs the
186-
// on-link route for the global mycelium subnet.
187-
let node_addr = crypto::PublicKey::from(&node_key).address();
188-
match crate::node_handle::create_tun_fd(
189-
&tun_name,
190-
node_addr,
191-
crate::GLOBAL_SUBNET_PREFIX_LEN,
192-
1400,
193-
) {
194-
Ok(fd) => fd,
195-
Err(e) => {
196-
error!("Failed to create TUN fd: {e}");
197-
error::set(format!("failed to create tun fd: {e}"));
198-
return std::ptr::null_mut();
199-
}
200-
}
201-
};
202-
#[cfg(any(target_os = "ios", all(target_os = "macos", feature = "mactunfd")))]
184+
#[cfg(any(
185+
target_os = "ios",
186+
all(target_os = "macos", feature = "mactunfd"),
187+
all(target_os = "android", feature = "androidtunfd"),
188+
))]
203189
let tun_fd = cfg.tun_fd;
204190

205191
let config = Config {
206-
node_key,
192+
node_key: crypto::SecretKey::from(cfg.priv_key),
207193
peers: endpoints,
208194
no_tun: false,
209195
#[cfg(any(
210196
target_os = "linux",
211197
all(target_os = "macos", not(feature = "mactunfd")),
212-
target_os = "windows"
198+
target_os = "windows",
199+
all(target_os = "android", not(feature = "androidtunfd")),
213200
))]
214201
tun_name,
215202
#[cfg(any(
216-
target_os = "android",
217203
target_os = "ios",
218204
all(target_os = "macos", feature = "mactunfd"),
205+
all(target_os = "android", feature = "androidtunfd"),
219206
))]
220207
tun_fd: Some(tun_fd),
221208
tcp_listen_port: cfg.tcp_listen_port,

mycelium/src/lib.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ pub struct Config<M> {
8484
#[cfg(any(
8585
target_os = "linux",
8686
all(target_os = "macos", not(feature = "mactunfd")),
87-
target_os = "windows"
87+
target_os = "windows",
88+
all(target_os = "android", not(feature = "androidtunfd")),
8889
))]
8990
pub tun_name: String,
9091

@@ -97,14 +98,16 @@ pub struct Config<M> {
9798
/// Mark that's set on all packets that we send on the underlying network
9899
pub firewall_mark: Option<u32>,
99100

100-
// tun_fd is android, iOS, macos on appstore specific option
101-
// We can't create TUN device from the Rust code in android, iOS, and macos on appstore.
102-
// So, we create the TUN device on Kotlin(android) or Swift(iOS, macos) then pass
103-
// the TUN's file descriptor to mycelium.
101+
// tun_fd is the iOS / macos-appstore / android-VpnService option.
102+
// We can't create the TUN device from Rust on iOS or macOS-appstore (the
103+
// platform doesn't expose `/dev/net/tun` to the app). Android apps using
104+
// `VpnService.Builder` are in the same situation — the framework hands
105+
// back a ready fd. In these cases the TUN is created by Kotlin (android)
106+
// or Swift (iOS, macOS) and the file descriptor is passed to mycelium.
104107
#[cfg(any(
105-
target_os = "android",
106108
target_os = "ios",
107109
all(target_os = "macos", feature = "mactunfd"),
110+
all(target_os = "android", feature = "androidtunfd"),
108111
))]
109112
pub tun_fd: Option<i32>,
110113

@@ -281,7 +284,8 @@ where
281284
#[cfg(any(
282285
target_os = "linux",
283286
all(target_os = "macos", not(feature = "mactunfd")),
284-
target_os = "windows"
287+
target_os = "windows",
288+
all(target_os = "android", not(feature = "androidtunfd")),
285289
))]
286290
let tun_config = TunConfig {
287291
name: config.tun_name.clone(),
@@ -291,9 +295,9 @@ where
291295
.expect("Static configured TUN route is valid; qed"),
292296
};
293297
#[cfg(any(
294-
target_os = "android",
295298
target_os = "ios",
296299
all(target_os = "macos", feature = "mactunfd"),
300+
all(target_os = "android", feature = "androidtunfd"),
297301
))]
298302
let tun_config = TunConfig {
299303
tun_fd: config.tun_fd.unwrap(),

0 commit comments

Comments
 (0)