Skip to content

Commit 703e7fd

Browse files
committed
Fully configure TUN interface in FFI Android path
The FFI's create_tun_fd only ran TUNSETIFF, but the downstream tun/android.rs assumes the fd is fully configured (matching the mobile/VpnService contract). Result: the interface had no IP, was administratively down, and could not carry traffic. Extend create_tun_fd to assign the node's IPv6 with the global subnet's prefix length (installs the on-link 400::/7 route), set MTU 1400 to match tun/android.rs's LINK_MTU, and bring the interface up via SIOCSIFFLAGS.
1 parent 61873bc commit 703e7fd

2 files changed

Lines changed: 140 additions & 11 deletions

File tree

mycelium/src/ffi/exports.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,20 +177,33 @@ pub unsafe extern "C" fn mycelium_start(
177177

178178
info!(peers = peers_strs.len(), "starting mycelium node");
179179

180+
let node_key = crypto::SecretKey::from(cfg.priv_key);
181+
180182
#[cfg(target_os = "android")]
181-
let tun_fd = match crate::node_handle::create_tun_fd(&tun_name) {
182-
Ok(fd) => fd,
183-
Err(e) => {
184-
error!("Failed to create TUN fd: {e}");
185-
error::set(format!("failed to create tun fd: {e}"));
186-
return std::ptr::null_mut();
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+
}
187200
}
188201
};
189202
#[cfg(any(target_os = "ios", all(target_os = "macos", feature = "mactunfd")))]
190203
let tun_fd = cfg.tun_fd;
191204

192205
let config = Config {
193-
node_key: crypto::SecretKey::from(cfg.priv_key),
206+
node_key,
194207
peers: endpoints,
195208
no_tun: false,
196209
#[cfg(any(

mycelium/src/node_handle.rs

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,30 @@ impl std::error::Error for NodeError {}
3838

3939
// ── TUN setup (Android only) ────────────────────────────────────────────────
4040

41-
/// On Android the `tun` crate expects an already-opened file descriptor.
42-
/// This function opens `/dev/tun`, configures the interface with TUNSETIFF,
43-
/// and returns the raw fd. Requires `CAP_NET_ADMIN`.
41+
/// Open and fully configure a TUN file descriptor on Android.
42+
///
43+
/// The `tun/android.rs` data-plane code expects an already-configured fd
44+
/// (the same contract the `mobile/` crate satisfies via `VpnService.Builder`).
45+
/// For the FFI path there is no VpnService doing that work, so this function
46+
/// does it: opens `/dev/tun`, sets `IFF_TUN | IFF_NO_PI` via `TUNSETIFF`,
47+
/// assigns `node_addr` with `prefix_len` (`/7` causes the kernel to install
48+
/// the on-link route for the global mycelium subnet), sets the MTU, and
49+
/// brings the interface up.
50+
///
51+
/// Requires `CAP_NET_ADMIN`.
4452
#[cfg(target_os = "android")]
45-
pub fn create_tun_fd(tun_name: &str) -> Result<i32, std::io::Error> {
53+
pub fn create_tun_fd(
54+
tun_name: &str,
55+
node_addr: std::net::Ipv6Addr,
56+
prefix_len: u8,
57+
mtu: i32,
58+
) -> Result<i32, std::io::Error> {
4659
const TUNSETIFF: libc::c_ulong = 0x400454ca;
4760
const IFF_TUN: libc::c_short = 0x0001;
4861
const IFF_NO_PI: libc::c_short = 0x1000;
4962

63+
// SAFETY: the path is a static NUL-terminated byte string; `open` has no
64+
// other preconditions.
5065
let fd = unsafe { libc::open(b"/dev/tun\0".as_ptr() as *const libc::c_char, libc::O_RDWR) };
5166
if fd < 0 {
5267
return Err(std::io::Error::last_os_error());
@@ -60,16 +75,117 @@ pub fn create_tun_fd(tun_name: &str) -> Result<i32, std::io::Error> {
6075
ifr[16] = (flags & 0xff) as u8;
6176
ifr[17] = ((flags >> 8) & 0xff) as u8;
6277

78+
// SAFETY: `fd` is a valid file descriptor that we just opened. `ifr` is a
79+
// 40-byte buffer matching the kernel's `struct ifreq` layout, with name
80+
// bytes (offset 0..15), a NUL terminator (offset 15, left at zero), and
81+
// `ifr_flags` (offset 16..18) populated; remaining bytes are zero, which
82+
// is what TUNSETIFF expects for unused union members.
6383
let ret = unsafe { libc::ioctl(fd, TUNSETIFF as i32, ifr.as_ptr()) };
6484
if ret < 0 {
6585
let err = std::io::Error::last_os_error();
86+
// SAFETY: `fd` is the open fd from the `open` call above; nothing else
87+
// can reference it because it has not escaped this function.
6688
unsafe { libc::close(fd) };
6789
return Err(err);
6890
}
6991

92+
if let Err(e) = configure_tun_interface(tun_name, node_addr, prefix_len, mtu) {
93+
// SAFETY: `fd` is the open fd from the `open` call above; nothing else
94+
// can reference it because it has not escaped this function.
95+
unsafe { libc::close(fd) };
96+
return Err(e);
97+
}
98+
7099
Ok(fd)
71100
}
72101

102+
/// Assign address, set MTU and bring the named interface up via an
103+
/// `AF_INET6` control socket. `SIOCSIFADDR` for IPv6 requires `AF_INET6`.
104+
#[cfg(target_os = "android")]
105+
fn configure_tun_interface(
106+
tun_name: &str,
107+
node_addr: std::net::Ipv6Addr,
108+
prefix_len: u8,
109+
mtu: i32,
110+
) -> std::io::Result<()> {
111+
// SAFETY: `socket(2)` has no preconditions; the arguments are valid
112+
// constants exported by libc.
113+
let sock = unsafe { libc::socket(libc::AF_INET6, libc::SOCK_DGRAM, 0) };
114+
if sock < 0 {
115+
return Err(std::io::Error::last_os_error());
116+
}
117+
118+
let result = (|| -> std::io::Result<()> {
119+
// SAFETY: `libc::ifreq` is a `repr(C)` aggregate of POD types and a
120+
// C union; the all-zero bit pattern is a valid value of every
121+
// variant (the union members are all integers or fixed-size byte
122+
// arrays), and the kernel ignores fields not consumed by the
123+
// specific ioctl request.
124+
let mut ifr: libc::ifreq = unsafe { std::mem::zeroed() };
125+
let name_bytes = tun_name.as_bytes();
126+
let copy_len = name_bytes.len().min(libc::IFNAMSIZ - 1);
127+
for (dst, &src) in ifr.ifr_name[..copy_len].iter_mut().zip(name_bytes) {
128+
*dst = src as libc::c_char;
129+
}
130+
131+
// SAFETY: `sock` is a valid socket fd opened above; `ifr` is fully
132+
// initialised with the interface name in `ifr_name` (NUL-terminated:
133+
// we copied at most IFNAMSIZ-1 bytes into a zero-initialised buffer)
134+
// — the layout expected by SIOCGIFINDEX.
135+
if unsafe { libc::ioctl(sock, libc::SIOCGIFINDEX as _, &mut ifr) } < 0 {
136+
return Err(std::io::Error::last_os_error());
137+
}
138+
// SAFETY: SIOCGIFINDEX populates the `ifru_ifindex` union variant on
139+
// success; the ioctl returned >= 0 above.
140+
let ifindex = unsafe { ifr.ifr_ifru.ifru_ifindex };
141+
142+
let ifr6 = libc::in6_ifreq {
143+
ifr6_addr: libc::in6_addr {
144+
s6_addr: node_addr.octets(),
145+
},
146+
ifr6_prefixlen: prefix_len as u32,
147+
ifr6_ifindex: ifindex,
148+
};
149+
// SAFETY: `sock` is a valid AF_INET6 socket fd (required for IPv6
150+
// SIOCSIFADDR); `ifr6` is a fully initialised `in6_ifreq` with the
151+
// address, prefix length, and interface index expected by the ioctl.
152+
if unsafe { libc::ioctl(sock, libc::SIOCSIFADDR as _, &ifr6) } < 0 {
153+
return Err(std::io::Error::last_os_error());
154+
}
155+
156+
ifr.ifr_ifru.ifru_mtu = mtu;
157+
// SAFETY: `sock` is a valid socket fd; `ifr` has the interface name
158+
// set in `ifr_name` and the MTU written into the `ifru_mtu` union
159+
// variant — the layout SIOCSIFMTU expects.
160+
if unsafe { libc::ioctl(sock, libc::SIOCSIFMTU as _, &ifr) } < 0 {
161+
return Err(std::io::Error::last_os_error());
162+
}
163+
164+
// SAFETY: `sock` is a valid socket fd; `ifr_name` is still set from
165+
// the SIOCGIFINDEX call above (the kernel only writes the `ifr_ifru`
166+
// union on success, leaving `ifr_name` intact).
167+
if unsafe { libc::ioctl(sock, libc::SIOCGIFFLAGS as _, &mut ifr) } < 0 {
168+
return Err(std::io::Error::last_os_error());
169+
}
170+
// SAFETY: SIOCGIFFLAGS populates the `ifru_flags` union variant on
171+
// success; the ioctl returned >= 0 above.
172+
ifr.ifr_ifru.ifru_flags = unsafe { ifr.ifr_ifru.ifru_flags } | libc::IFF_UP as libc::c_short;
173+
// SAFETY: `sock` is a valid socket fd; `ifr` has the interface name
174+
// set and the updated flags written into the `ifru_flags` union
175+
// variant.
176+
if unsafe { libc::ioctl(sock, libc::SIOCSIFFLAGS as _, &ifr) } < 0 {
177+
return Err(std::io::Error::last_os_error());
178+
}
179+
180+
Ok(())
181+
})();
182+
183+
// SAFETY: `sock` is the fd from the `socket` call above; it has not
184+
// escaped this function and is no longer used.
185+
unsafe { libc::close(sock) };
186+
result
187+
}
188+
73189
// ── NodeHandle ──────────────────────────────────────────────────────────────
74190

75191
/// Upper bound on how long node teardown waits for the background Tokio

0 commit comments

Comments
 (0)