Skip to main content

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}