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}