Skip to main content

ios_core/tunnel/
manager.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use tokio::sync::{watch, RwLock};
5
6#[cfg(feature = "tunnel-userspace")]
7use crate::tunnel::tun::userspace::UserspaceTunDevice;
8use crate::tunnel::TunnelInfo;
9
10/// TUN mode selection.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum TunMode {
13    /// Use the OS kernel TUN device (requires admin/root on most platforms).
14    Kernel,
15    /// Use smoltcp userspace network stack (no special privileges needed).
16    #[default]
17    Userspace,
18}
19
20/// A live tunnel handle. Dropping this handle signals the tunnel task to stop.
21pub struct TunnelHandle {
22    pub udid: String,
23    pub info: TunnelInfo,
24    pub userspace_port: Option<u16>,
25    _runtime: TunnelRuntime,
26}
27
28enum TunnelRuntime {
29    Kernel {
30        /// Dropping this sender cancels the tunnel (receivers get RecvError).
31        _cancel_tx: watch::Sender<()>,
32    },
33    #[cfg(feature = "tunnel-userspace")]
34    Userspace { _runtime: UserspaceTunDevice },
35}
36
37impl TunnelHandle {
38    pub fn new(
39        udid: String,
40        info: TunnelInfo,
41        userspace_port: Option<u16>,
42    ) -> (Self, watch::Receiver<()>) {
43        let (tx, rx) = watch::channel(());
44        (
45            Self {
46                udid,
47                info,
48                userspace_port,
49                _runtime: TunnelRuntime::Kernel { _cancel_tx: tx },
50            },
51            rx,
52        )
53    }
54
55    #[cfg(feature = "tunnel-userspace")]
56    pub fn new_userspace(udid: String, info: TunnelInfo, runtime: UserspaceTunDevice) -> Self {
57        Self {
58            udid,
59            info,
60            userspace_port: Some(runtime.local_port),
61            _runtime: TunnelRuntime::Userspace { _runtime: runtime },
62        }
63    }
64
65    pub fn is_alive(&self) -> bool {
66        match &self._runtime {
67            TunnelRuntime::Kernel { _cancel_tx } => _cancel_tx.receiver_count() > 0,
68            #[cfg(feature = "tunnel-userspace")]
69            TunnelRuntime::Userspace { _runtime } => _runtime.is_alive(),
70        }
71    }
72}
73
74/// Manager for active tunnel instances.
75#[derive(Clone)]
76pub struct TunnelManager {
77    tunnels: Arc<RwLock<HashMap<String, Arc<TunnelHandle>>>>,
78    pub mode: TunMode,
79}
80
81impl TunnelManager {
82    pub fn new(mode: TunMode) -> Self {
83        Self {
84            tunnels: Arc::new(RwLock::new(HashMap::new())),
85            mode,
86        }
87    }
88
89    pub async fn register(&self, handle: Arc<TunnelHandle>) {
90        self.tunnels
91            .write()
92            .await
93            .insert(handle.udid.clone(), handle);
94    }
95
96    pub async fn list(&self) -> Vec<Arc<TunnelHandle>> {
97        self.tunnels.read().await.values().cloned().collect()
98    }
99
100    pub async fn find(&self, udid: &str) -> Option<Arc<TunnelHandle>> {
101        self.tunnels.read().await.get(udid).cloned()
102    }
103
104    /// Remove and drop the tunnel handle (which cancels the tunnel task). Returns true if found.
105    pub async fn stop(&self, udid: &str) -> bool {
106        self.tunnels.write().await.remove(udid).is_some()
107    }
108}
109
110impl Default for TunnelManager {
111    fn default() -> Self {
112        Self::new(TunMode::Userspace)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use std::sync::Arc;
119
120    use super::*;
121
122    fn tunnel_info() -> TunnelInfo {
123        TunnelInfo {
124            server_address: "fd59:2381:6956::1".into(),
125            server_rsd_port: 58783,
126            client_address: "fd59:2381:6956::2".into(),
127            client_mtu: 1280,
128        }
129    }
130
131    #[test]
132    fn tunnel_handle_kernel_liveness_tracks_cancel_receiver() {
133        let (handle, cancel_rx) = TunnelHandle::new("test-udid".into(), tunnel_info(), None);
134
135        assert!(handle.is_alive());
136
137        drop(cancel_rx);
138        assert!(!handle.is_alive());
139    }
140
141    #[tokio::test]
142    async fn tunnel_manager_register_find_list_and_stop() {
143        let manager = TunnelManager::new(TunMode::Kernel);
144        let (handle, cancel_rx) = TunnelHandle::new("test-udid".into(), tunnel_info(), None);
145        let handle = Arc::new(handle);
146
147        manager.register(handle.clone()).await;
148
149        assert!(Arc::ptr_eq(
150            &manager.find("test-udid").await.unwrap(),
151            &handle
152        ));
153        assert!(manager.find("missing").await.is_none());
154        assert_eq!(manager.list().await.len(), 1);
155        assert_eq!(manager.list().await[0].udid, "test-udid");
156
157        assert!(manager.stop("test-udid").await);
158        assert!(!manager.stop("test-udid").await);
159        assert!(manager.find("test-udid").await.is_none());
160        assert!(manager.list().await.is_empty());
161
162        drop(cancel_rx);
163    }
164
165    #[test]
166    fn tunnel_manager_default_uses_userspace_mode() {
167        let manager = TunnelManager::default();
168        assert_eq!(manager.mode, TunMode::Userspace);
169    }
170}