Skip to main content

tun_rs/async_device/
mod.rs

1/*!
2# Asynchronous Device Module
3
4This module provides asynchronous I/O support for TUN/TAP interfaces through the [`AsyncDevice`] type.
5
6## Overview
7
8The async module enables non-blocking I/O operations on TUN/TAP devices, allowing you to efficiently
9handle network traffic in async/await contexts. Two async runtime backends are supported:
10
11- **Tokio**: Enable with the `async` or `async_tokio` feature
12- **async-io**: Enable with the `async_io` feature (for async-std, smol, etc.)
13
14**Important**: You must choose exactly one async runtime. Enabling both simultaneously will result
15in a compile error.
16
17## Feature Flags
18
19- `async` (alias for `async_tokio`) - Use Tokio runtime
20- `async_tokio` - Use Tokio runtime explicitly
21- `async_io` - Use async-io runtime (for async-std, smol, and similar runtimes)
22- `async_framed` - Enable framed I/O support with futures (requires one of the above)
23
24## Usage with Tokio
25
26Add to your `Cargo.toml`:
27
28```toml
29[dependencies]
30tun-rs = { version = "2", features = ["async"] }
31tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
32```
33
34Example:
35
36```no_run
37use tun_rs::DeviceBuilder;
38
39#[tokio::main]
40async fn main() -> std::io::Result<()> {
41    let dev = DeviceBuilder::new()
42        .ipv4("10.0.0.1", 24, None)
43        .build_async()?;
44
45    let mut buf = vec![0u8; 65536];
46    loop {
47        let len = dev.recv(&mut buf).await?;
48        println!("Received {} bytes", len);
49
50        // Echo the packet back
51        dev.send(&buf[..len]).await?;
52    }
53}
54```
55
56## Usage with async-std
57
58Add to your `Cargo.toml`:
59
60```toml
61[dependencies]
62tun-rs = { version = "2", features = ["async_io"] }
63async-std = { version = "1", features = ["attributes"] }
64```
65
66Example:
67
68```no_run
69use tun_rs::DeviceBuilder;
70
71#[async_std::main]
72async fn main() -> std::io::Result<()> {
73    let dev = DeviceBuilder::new()
74        .ipv4("10.0.0.1", 24, None)
75        .build_async()?;
76
77    let mut buf = vec![0u8; 65536];
78    loop {
79        let len = dev.recv(&mut buf).await?;
80        println!("Received {} bytes", len);
81    }
82}
83```
84
85## Device Types
86
87### `AsyncDevice`
88
89The main async device type. Created via `DeviceBuilder::build_async()`.
90Takes ownership of the underlying file descriptor and closes it when dropped.
91
92### `BorrowedAsyncDevice`
93
94A borrowed variant that does not take ownership of the file descriptor.
95Useful when the file descriptor is managed externally (e.g., on mobile platforms).
96
97```no_run
98# #[cfg(unix)]
99# {
100use tun_rs::BorrowedAsyncDevice;
101
102async fn use_borrowed_fd(fd: std::os::fd::RawFd) -> std::io::Result<()> {
103    // SAFETY: fd must be a valid, open file descriptor
104    // This does NOT take ownership and will NOT close fd
105    let dev = unsafe { BorrowedAsyncDevice::borrow_raw(fd)? };
106
107    let mut buf = vec![0u8; 1500];
108    let len = dev.recv(&mut buf).await?;
109
110    // fd is still valid after dev is dropped
111    Ok(())
112}
113# }
114```
115
116## API Methods
117
118### I/O Operations
119
120- `recv(&self, buf: &mut [u8]) -> impl Future<Output = io::Result<usize>>`
121  - Asynchronously read a packet from the device
122
123- `send(&self, buf: &[u8]) -> impl Future<Output = io::Result<usize>>`
124  - Asynchronously send a packet to the device
125
126### Readiness Operations
127
128- `readable(&self) -> impl Future<Output = io::Result<()>>`
129  - Wait until the device is readable
130
131- `writable(&self) -> impl Future<Output = io::Result<()>>`
132  - Wait until the device is writable
133
134These are useful for implementing custom I/O logic or integrating with other async primitives.
135
136## Platform Support
137
138Async I/O is supported on:
139- Linux
140- macOS
141- Windows
142- FreeBSD, OpenBSD, NetBSD
143- Android, iOS (via borrowed file descriptors)
144
145## Performance Considerations
146
147- Async I/O is efficient for handling multiple connections or high concurrency
148- For single-threaded blocking I/O, consider using [`crate::SyncDevice`] instead
149- On Linux with offload enabled, use `recv_multiple`/`send_multiple` for best throughput
150- Buffer sizes should be at least MTU + header overhead (typically 1500-65536 bytes)
151
152## Error Handling
153
154All async operations return `io::Result` types. Common errors include:
155- `WouldBlock` (internally handled by async runtime)
156- `Interrupted` (operation was interrupted, can be retried)
157- `BrokenPipe` (device was closed)
158- Platform-specific errors
159
160## Safety Considerations
161
162The `from_fd` and `borrow_raw` methods are `unsafe` because they:
163- Require a valid, open file descriptor
164- Can lead to double-close bugs if ownership is not managed correctly
165- May cause undefined behavior if the fd is not a valid TUN/TAP device
166
167Always ensure proper lifetime management when using these methods.
168*/
169
170#[cfg(unix)]
171pub(crate) mod unix;
172#[cfg(all(unix, not(target_os = "macos")))]
173pub use unix::AsyncDevice;
174#[cfg(target_os = "macos")]
175mod macos;
176#[cfg(target_os = "macos")]
177pub use macos::AsyncDevice;
178#[cfg(windows)]
179mod windows;
180#[cfg(windows)]
181pub use windows::AsyncDevice;
182
183#[cfg(all(
184    any(feature = "async_io", feature = "async_tokio"),
185    feature = "async_framed"
186))]
187#[cfg_attr(
188    docsrs,
189    doc(cfg(all(
190        any(feature = "async_io", feature = "async_tokio"),
191        feature = "async_framed"
192    )))
193)]
194pub mod async_framed;
195
196#[cfg(all(feature = "async_tokio", feature = "async_io", not(doc)))]
197compile_error! {"More than one asynchronous runtime is simultaneously specified in features"}
198
199/// A borrowed asynchronous TUN/TAP device.
200///
201/// This type wraps an [`AsyncDevice`] but does not take ownership of the underlying file descriptor.
202/// It's designed for scenarios where the file descriptor is managed externally, such as:
203///
204/// - iOS PacketTunnelProvider (NetworkExtension framework)
205/// - Android VpnService
206/// - Other FFI scenarios where file descriptor ownership is managed by foreign code
207///
208/// # Ownership and Lifetime
209///
210/// Unlike [`AsyncDevice`], `BorrowedAsyncDevice`:
211/// - Does NOT close the file descriptor when dropped
212/// - Requires the caller to manage the file descriptor's lifetime
213/// - Must not outlive the actual file descriptor
214///
215/// # Example
216///
217/// ```no_run
218/// # #[cfg(unix)]
219/// # async fn example(fd: std::os::fd::RawFd) -> std::io::Result<()> {
220/// use tun_rs::BorrowedAsyncDevice;
221///
222/// // SAFETY: Caller must ensure fd is valid and remains open
223/// let device = unsafe { BorrowedAsyncDevice::borrow_raw(fd)? };
224///
225/// let mut buffer = vec![0u8; 1500];
226/// let n = device.recv(&mut buffer).await?;
227/// println!("Received {} bytes", n);
228///
229/// // fd is still valid after device is dropped
230/// # Ok(())
231/// # }
232/// ```
233///
234/// # Safety
235///
236/// When using `borrow_raw`, you must ensure:
237/// 1. The file descriptor is valid and open
238/// 2. The file descriptor is a TUN/TAP device
239/// 3. The file descriptor outlives the `BorrowedAsyncDevice`
240/// 4. No other code closes the file descriptor while in use
241#[cfg(unix)]
242pub struct BorrowedAsyncDevice<'dev> {
243    dev: AsyncDevice,
244    _phantom: std::marker::PhantomData<&'dev AsyncDevice>,
245}
246#[cfg(unix)]
247impl std::ops::Deref for BorrowedAsyncDevice<'_> {
248    type Target = AsyncDevice;
249    fn deref(&self) -> &Self::Target {
250        &self.dev
251    }
252}
253#[cfg(unix)]
254impl BorrowedAsyncDevice<'_> {
255    /// Borrows an existing file descriptor without taking ownership.
256    ///
257    /// Creates a `BorrowedAsyncDevice` from a raw file descriptor. The file descriptor
258    /// will **not** be closed when this device is dropped - the caller retains ownership
259    /// and is responsible for closing it.
260    ///
261    /// # Safety
262    ///
263    /// The caller must ensure that:
264    ///
265    /// - `fd` is a valid, open file descriptor
266    /// - `fd` refers to a TUN/TAP device (not a regular file, socket, etc.)
267    /// - `fd` remains open for the lifetime of the returned `BorrowedAsyncDevice`
268    /// - No other code attempts to close `fd` while the device is in use
269    /// - The file descriptor is not used in conflicting ways (e.g., both blocking and non-blocking)
270    ///
271    /// Violating these requirements may result in:
272    /// - I/O errors
273    /// - Undefined behavior (in case of use-after-close)
274    /// - Resource leaks (if the original fd is never closed)
275    ///
276    /// # Example
277    ///
278    /// ```no_run
279    /// # #[cfg(unix)]
280    /// # async fn example() -> std::io::Result<()> {
281    /// use std::os::fd::RawFd;
282    /// use tun_rs::BorrowedAsyncDevice;
283    ///
284    /// // Obtain fd from iOS PacketTunnelProvider or Android VpnService
285    /// let fd: RawFd = get_vpn_fd(); // exposition-only
286    ///
287    /// // SAFETY: fd is valid and managed by the OS framework
288    /// let device = unsafe { BorrowedAsyncDevice::borrow_raw(fd)? };
289    ///
290    /// // Use the device...
291    /// let mut buf = vec![0u8; 1500];
292    /// let n = device.recv(&mut buf).await?;
293    ///
294    /// // device is dropped here, but fd remains valid
295    /// // Caller must close fd when done
296    ///
297    /// # fn get_vpn_fd() -> RawFd { 0 }
298    /// # Ok(())
299    /// # }
300    /// ```
301    ///
302    /// # Errors
303    ///
304    /// Returns an error if the file descriptor cannot be configured for async I/O.
305    /// Common causes:
306    /// - Invalid file descriptor
307    /// - File descriptor does not refer to a TUN/TAP device
308    /// - Platform-specific configuration failures
309    pub unsafe fn borrow_raw(fd: std::os::fd::RawFd) -> std::io::Result<Self> {
310        #[allow(unused_unsafe)]
311        unsafe {
312            Ok(Self {
313                dev: AsyncDevice::borrow_raw(fd)?,
314                _phantom: std::marker::PhantomData,
315            })
316        }
317    }
318}