tun_rs/platform/unix/interrupt.rs
1/*!
2# Interruptible I/O Module
3
4This module provides support for interruptible I/O operations on Unix platforms.
5
6## Overview
7
8Interruptible I/O allows you to cancel blocking read/write operations on a TUN/TAP device
9from another thread. This is useful for graceful shutdown, implementing timeouts, or
10responding to signals.
11
12## Availability
13
14This module is only available when the `interruptible` feature flag is enabled:
15
16```toml
17[dependencies]
18tun-rs = { version = "2", features = ["interruptible"] }
19```
20
21## How It Works
22
23The implementation uses a pipe-based signaling mechanism:
24- An `InterruptEvent` creates a pipe internally
25- When triggered, it writes to the pipe
26- I/O operations use `poll()` to wait on both the device fd and the pipe
27- If the pipe becomes readable, the I/O operation returns with `ErrorKind::Interrupted`
28
29## Usage
30
31```no_run
32# #[cfg(all(unix, feature = "interruptible"))]
33# {
34use tun_rs::{DeviceBuilder, InterruptEvent};
35use std::sync::Arc;
36use std::thread;
37
38let dev = DeviceBuilder::new()
39 .ipv4("10.0.0.1", 24, None)
40 .build_sync()?;
41
42let event = Arc::new(InterruptEvent::new()?);
43let event_clone = event.clone();
44
45// Spawn a thread that will read from the device
46let handle = thread::spawn(move || {
47 let mut buf = vec![0u8; 1500];
48 match dev.recv_intr(&mut buf, &event_clone) {
49 Ok(n) => println!("Received {} bytes", n),
50 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
51 println!("Read was interrupted");
52 }
53 Err(e) => eprintln!("Error: {}", e),
54 }
55});
56
57// From the main thread, trigger the interrupt
58thread::sleep(std::time::Duration::from_secs(1));
59event.trigger()?;
60
61handle.join().unwrap();
62# }
63# Ok::<(), std::io::Error>(())
64```
65
66## Performance Considerations
67
68- Interruptible I/O has slightly more overhead than regular I/O due to the additional poll() fd
69- The pipe is created once and reused across all operations
70- Non-blocking mode is set on the pipe fds to prevent deadlocks
71
72## Platform Support
73
74- **Linux**: Uses `poll()` with two file descriptors
75- **macOS**: Uses `poll()` like other Unix targets
76- **FreeBSD/OpenBSD/NetBSD**: Uses `poll()` like Linux
77- **Windows**: Not supported (would need IOCP or overlapped I/O)
78
79## Thread Safety
80
81`InterruptEvent` is thread-safe and can be shared across threads using `Arc`.
82*/
83
84use crate::platform::unix::Fd;
85use std::io;
86use std::io::{IoSlice, IoSliceMut};
87use std::os::fd::AsRawFd;
88use std::sync::Mutex;
89
90impl Fd {
91 pub(crate) fn read_interruptible(
92 &self,
93 buf: &mut [u8],
94 event: &InterruptEvent,
95 timeout: Option<std::time::Duration>,
96 ) -> io::Result<usize> {
97 loop {
98 self.wait_readable_interruptible(event, timeout)?;
99 return match self.read(buf) {
100 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
101 continue;
102 }
103 rs => rs,
104 };
105 }
106 }
107 pub(crate) fn readv_interruptible(
108 &self,
109 bufs: &mut [IoSliceMut<'_>],
110 event: &InterruptEvent,
111 timeout: Option<std::time::Duration>,
112 ) -> io::Result<usize> {
113 loop {
114 self.wait_readable_interruptible(event, timeout)?;
115 return match self.readv(bufs) {
116 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
117 continue;
118 }
119
120 rs => rs,
121 };
122 }
123 }
124 pub(crate) fn write_interruptible(
125 &self,
126 buf: &[u8],
127 event: &InterruptEvent,
128 ) -> io::Result<usize> {
129 loop {
130 self.wait_writable_interruptible(event)?;
131 return match self.write(buf) {
132 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
133 continue;
134 }
135 rs => rs,
136 };
137 }
138 }
139 pub fn writev_interruptible(
140 &self,
141 bufs: &[IoSlice<'_>],
142 event: &InterruptEvent,
143 ) -> io::Result<usize> {
144 loop {
145 self.wait_writable_interruptible(event)?;
146 return match self.writev(bufs) {
147 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
148 continue;
149 }
150 rs => rs,
151 };
152 }
153 }
154 pub fn wait_readable_interruptible(
155 &self,
156 interrupted_event: &InterruptEvent,
157 timeout: Option<std::time::Duration>,
158 ) -> io::Result<()> {
159 let fd = self.as_raw_fd() as libc::c_int;
160 let event_fd = interrupted_event.as_event_fd();
161
162 let mut fds = [
163 libc::pollfd {
164 fd,
165 events: libc::POLLIN,
166 revents: 0,
167 },
168 libc::pollfd {
169 fd: event_fd,
170 events: libc::POLLIN,
171 revents: 0,
172 },
173 ];
174
175 let result = unsafe {
176 libc::poll(
177 fds.as_mut_ptr(),
178 fds.len() as libc::nfds_t,
179 timeout
180 .map(|t| t.as_millis().min(i32::MAX as _) as _)
181 .unwrap_or(-1),
182 )
183 };
184
185 if result == -1 {
186 return Err(io::Error::last_os_error());
187 }
188 if result == 0 {
189 return Err(io::Error::from(io::ErrorKind::TimedOut));
190 }
191 if fds[0].revents & libc::POLLIN != 0 {
192 return Ok(());
193 }
194
195 if fds[1].revents & libc::POLLIN != 0 {
196 return Err(io::Error::new(
197 io::ErrorKind::Interrupted,
198 "trigger interrupt",
199 ));
200 }
201
202 Err(io::Error::other("fd error"))
203 }
204 pub fn wait_writable_interruptible(
205 &self,
206 interrupted_event: &InterruptEvent,
207 ) -> io::Result<()> {
208 let fd = self.as_raw_fd() as libc::c_int;
209 let event_fd = interrupted_event.as_event_fd();
210
211 let mut fds = [
212 libc::pollfd {
213 fd,
214 events: libc::POLLOUT,
215 revents: 0,
216 },
217 libc::pollfd {
218 fd: event_fd,
219 events: libc::POLLIN,
220 revents: 0,
221 },
222 ];
223
224 let result = unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, -1) };
225
226 if result == -1 {
227 return Err(io::Error::last_os_error());
228 }
229 if fds[0].revents & libc::POLLOUT != 0 {
230 return Ok(());
231 }
232
233 if fds[1].revents & libc::POLLIN != 0 {
234 return Err(io::Error::new(
235 io::ErrorKind::Interrupted,
236 "trigger interrupt",
237 ));
238 }
239
240 Err(io::Error::other("fd error"))
241 }
242}
243
244#[cfg(target_os = "macos")]
245impl Fd {
246 pub fn wait_writable(
247 &self,
248 interrupt_event: Option<&InterruptEvent>,
249 timeout: Option<std::time::Duration>,
250 ) -> io::Result<()> {
251 self.wait(libc::POLLOUT, interrupt_event, timeout)
252 }
253 pub fn wait_readable(
254 &self,
255 interrupt_event: Option<&InterruptEvent>,
256 timeout: Option<std::time::Duration>,
257 ) -> io::Result<()> {
258 self.wait(libc::POLLIN, interrupt_event, timeout)
259 }
260 fn wait(
261 &self,
262 device_events: libc::c_short,
263 interrupt_event: Option<&InterruptEvent>,
264 timeout: Option<std::time::Duration>,
265 ) -> io::Result<()> {
266 let fd = self.as_raw_fd();
267 let mut fds = Vec::with_capacity(if interrupt_event.is_some() { 2 } else { 1 });
268 fds.push(libc::pollfd {
269 fd,
270 events: device_events,
271 revents: 0,
272 });
273 if let Some(interrupt_event) = interrupt_event {
274 fds.push(libc::pollfd {
275 fd: interrupt_event.as_event_fd(),
276 events: libc::POLLIN,
277 revents: 0,
278 });
279 }
280 let timeout_ms = timeout
281 .map(|t| t.as_millis().min(i32::MAX as u128) as libc::c_int)
282 .unwrap_or(-1);
283
284 let result = unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, timeout_ms) };
285 if result < 0 {
286 return Err(io::Error::last_os_error());
287 }
288 if result == 0 {
289 return Err(io::Error::from(io::ErrorKind::TimedOut));
290 }
291 if interrupt_event.is_some() && fds[1].revents & libc::POLLIN != 0 {
292 return Err(io::Error::new(
293 io::ErrorKind::Interrupted,
294 "trigger interrupt",
295 ));
296 }
297 if fds[0].revents & device_events != 0 {
298 return Ok(());
299 }
300 Err(io::Error::other("fd error"))
301 }
302}
303/// Event object for interrupting blocking I/O operations.
304///
305/// `InterruptEvent` provides a mechanism to cancel blocking read/write operations
306/// from another thread. It uses a pipe-based signaling mechanism internally.
307///
308/// # Thread Safety
309///
310/// This type is thread-safe and can be shared across threads using `Arc<InterruptEvent>`.
311///
312/// # Examples
313///
314/// Basic usage with interruptible read:
315///
316/// ```no_run
317/// # #[cfg(all(unix, feature = "interruptible"))]
318/// # {
319/// use std::sync::Arc;
320/// use std::thread;
321/// use std::time::Duration;
322/// use tun_rs::{DeviceBuilder, InterruptEvent};
323///
324/// let device = DeviceBuilder::new()
325/// .ipv4("10.0.0.1", 24, None)
326/// .build_sync()?;
327///
328/// let event = Arc::new(InterruptEvent::new()?);
329/// let event_clone = event.clone();
330///
331/// let reader = thread::spawn(move || {
332/// let mut buf = vec![0u8; 1500];
333/// device.recv_intr(&mut buf, &event_clone)
334/// });
335///
336/// // Give the reader time to start blocking
337/// thread::sleep(Duration::from_millis(100));
338///
339/// // Trigger the interrupt
340/// event.trigger()?;
341///
342/// match reader.join().unwrap() {
343/// Ok(n) => println!("Read {} bytes", n),
344/// Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
345/// println!("Successfully interrupted!");
346/// }
347/// Err(e) => eprintln!("Error: {}", e),
348/// }
349/// # }
350/// # Ok::<(), std::io::Error>(())
351/// ```
352///
353/// Using with a timeout:
354///
355/// ```no_run
356/// # #[cfg(all(unix, feature = "interruptible"))]
357/// # {
358/// use std::time::Duration;
359/// use tun_rs::{DeviceBuilder, InterruptEvent};
360///
361/// let device = DeviceBuilder::new()
362/// .ipv4("10.0.0.1", 24, None)
363/// .build_sync()?;
364///
365/// let event = InterruptEvent::new()?;
366/// let mut buf = vec![0u8; 1500];
367///
368/// // Will return an error if no data arrives within 5 seconds
369/// match device.recv_intr_timeout(&mut buf, &event, Some(Duration::from_secs(5))) {
370/// Ok(n) => println!("Read {} bytes", n),
371/// Err(e) if e.kind() == std::io::ErrorKind::TimedOut => {
372/// println!("Timed out waiting for data");
373/// }
374/// Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
375/// println!("Operation was interrupted");
376/// }
377/// Err(e) => eprintln!("Error: {}", e),
378/// }
379/// # }
380/// # Ok::<(), std::io::Error>(())
381/// ```
382///
383/// # Implementation Details
384///
385/// - Uses a Unix pipe for signaling
386/// - Both read and write ends are set to non-blocking mode
387/// - State is protected by a mutex to prevent race conditions
388/// - Once triggered, the event remains triggered until reset
389pub struct InterruptEvent {
390 state: Mutex<i32>,
391 read_fd: Fd,
392 write_fd: Fd,
393}
394impl InterruptEvent {
395 /// Creates a new `InterruptEvent`.
396 ///
397 /// This allocates a Unix pipe for signaling and sets both ends to non-blocking mode.
398 ///
399 /// # Errors
400 ///
401 /// Returns an error if:
402 /// - The pipe creation fails (e.g., out of file descriptors)
403 /// - Setting non-blocking mode fails
404 ///
405 /// # Example
406 ///
407 /// ```no_run
408 /// # #[cfg(all(unix, feature = "interruptible"))]
409 /// # {
410 /// use tun_rs::InterruptEvent;
411 ///
412 /// let event = InterruptEvent::new()?;
413 /// # }
414 /// # Ok::<(), std::io::Error>(())
415 /// ```
416 pub fn new() -> io::Result<Self> {
417 let mut fds: [libc::c_int; 2] = [0; 2];
418
419 unsafe {
420 if libc::pipe(fds.as_mut_ptr()) == -1 {
421 return Err(io::Error::last_os_error());
422 }
423 let read_fd = Fd::new_unchecked(fds[0]);
424 let write_fd = Fd::new_unchecked(fds[1]);
425 write_fd.set_nonblocking(true)?;
426 read_fd.set_nonblocking(true)?;
427 Ok(Self {
428 state: Mutex::new(0),
429 read_fd,
430 write_fd,
431 })
432 }
433 }
434
435 /// Triggers the interrupt event with value 1.
436 ///
437 /// This will cause any blocking I/O operations waiting on this event to return
438 /// with `ErrorKind::Interrupted`.
439 ///
440 /// Calling `trigger()` multiple times before `reset()` has no additional effect -
441 /// the event remains triggered.
442 ///
443 /// # Errors
444 ///
445 /// Returns an error if writing to the internal pipe fails, though this is rare
446 /// in practice (pipe write errors are usually `WouldBlock`, which is handled).
447 ///
448 /// # Example
449 ///
450 /// ```no_run
451 /// # #[cfg(all(unix, feature = "interruptible"))]
452 /// # {
453 /// use std::sync::Arc;
454 /// use std::thread;
455 /// use tun_rs::{DeviceBuilder, InterruptEvent};
456 ///
457 /// let event = Arc::new(InterruptEvent::new()?);
458 /// let event_clone = event.clone();
459 ///
460 /// thread::spawn(move || {
461 /// // ... blocking I/O with event_clone ...
462 /// });
463 ///
464 /// // Interrupt the operation
465 /// event.trigger()?;
466 /// # }
467 /// # Ok::<(), std::io::Error>(())
468 /// ```
469 pub fn trigger(&self) -> io::Result<()> {
470 self.trigger_value(1)
471 }
472
473 /// Triggers the interrupt event with a specific value.
474 ///
475 /// Similar to [`trigger()`](Self::trigger), but allows specifying a custom value
476 /// that can be retrieved with [`value()`](Self::value).
477 ///
478 /// # Arguments
479 ///
480 /// * `val` - The value to store (must be non-zero)
481 ///
482 /// # Errors
483 ///
484 /// - Returns `ErrorKind::InvalidInput` if `val` is 0
485 /// - Returns an error if writing to the pipe fails
486 ///
487 /// # Example
488 ///
489 /// ```no_run
490 /// # #[cfg(all(unix, feature = "interruptible"))]
491 /// # {
492 /// use tun_rs::InterruptEvent;
493 ///
494 /// let event = InterruptEvent::new()?;
495 ///
496 /// // Trigger with a custom value (e.g., signal number)
497 /// event.trigger_value(15)?; // SIGTERM
498 ///
499 /// // Later, check what value was used
500 /// assert_eq!(event.value(), 15);
501 /// # }
502 /// # Ok::<(), std::io::Error>(())
503 /// ```
504 pub fn trigger_value(&self, val: i32) -> io::Result<()> {
505 if val == 0 {
506 return Err(io::Error::new(
507 io::ErrorKind::InvalidInput,
508 "value cannot be 0",
509 ));
510 }
511 let mut guard = self.state.lock().unwrap();
512 if *guard != 0 {
513 return Ok(());
514 }
515 *guard = val;
516 let buf: [u8; 8] = 1u64.to_ne_bytes();
517 let res = unsafe {
518 libc::write(
519 self.write_fd.as_raw_fd(),
520 buf.as_ptr() as *const _,
521 buf.len(),
522 )
523 };
524 if res == -1 {
525 let e = io::Error::last_os_error();
526 if e.kind() == io::ErrorKind::WouldBlock {
527 return Ok(());
528 }
529 Err(e)
530 } else {
531 Ok(())
532 }
533 }
534 /// Checks if the event has been triggered.
535 ///
536 /// Returns `true` if the event is currently in the triggered state (value is non-zero).
537 ///
538 /// # Example
539 ///
540 /// ```no_run
541 /// # #[cfg(all(unix, feature = "interruptible"))]
542 /// # {
543 /// use tun_rs::InterruptEvent;
544 ///
545 /// let event = InterruptEvent::new()?;
546 /// assert!(!event.is_trigger());
547 ///
548 /// event.trigger()?;
549 /// assert!(event.is_trigger());
550 ///
551 /// event.reset()?;
552 /// assert!(!event.is_trigger());
553 /// # }
554 /// # Ok::<(), std::io::Error>(())
555 /// ```
556 pub fn is_trigger(&self) -> bool {
557 *self.state.lock().unwrap() != 0
558 }
559
560 /// Returns the current trigger value.
561 ///
562 /// Returns 0 if the event is not triggered, or the value passed to
563 /// [`trigger_value()`](Self::trigger_value) if triggered.
564 ///
565 /// # Example
566 ///
567 /// ```no_run
568 /// # #[cfg(all(unix, feature = "interruptible"))]
569 /// # {
570 /// use tun_rs::InterruptEvent;
571 ///
572 /// let event = InterruptEvent::new()?;
573 /// assert_eq!(event.value(), 0);
574 ///
575 /// event.trigger_value(42)?;
576 /// assert_eq!(event.value(), 42);
577 /// # }
578 /// # Ok::<(), std::io::Error>(())
579 /// ```
580 pub fn value(&self) -> i32 {
581 *self.state.lock().unwrap()
582 }
583
584 /// Resets the event to the non-triggered state.
585 ///
586 /// This drains any pending data from the internal pipe and sets the state back to 0.
587 /// After calling `reset()`, the event can be triggered again.
588 ///
589 /// # Errors
590 ///
591 /// Returns an error if reading from the pipe fails (other than `WouldBlock`).
592 ///
593 /// # Example
594 ///
595 /// ```no_run
596 /// # #[cfg(all(unix, feature = "interruptible"))]
597 /// # {
598 /// use tun_rs::InterruptEvent;
599 ///
600 /// let event = InterruptEvent::new()?;
601 ///
602 /// event.trigger()?;
603 /// assert!(event.is_trigger());
604 ///
605 /// event.reset()?;
606 /// assert!(!event.is_trigger());
607 ///
608 /// // Can trigger again after reset
609 /// event.trigger()?;
610 /// assert!(event.is_trigger());
611 /// # }
612 /// # Ok::<(), std::io::Error>(())
613 /// ```
614 pub fn reset(&self) -> io::Result<()> {
615 let mut buf = [0; 8];
616 let mut guard = self.state.lock().unwrap();
617 *guard = 0;
618 loop {
619 unsafe {
620 let res = libc::read(
621 self.read_fd.as_raw_fd(),
622 buf.as_mut_ptr() as *mut _,
623 buf.len(),
624 );
625 if res == -1 {
626 let error = io::Error::last_os_error();
627 return if error.kind() == io::ErrorKind::WouldBlock {
628 Ok(())
629 } else {
630 Err(error)
631 };
632 }
633 }
634 }
635 }
636 fn as_event_fd(&self) -> libc::c_int {
637 self.read_fd.as_raw_fd()
638 }
639}