wintun_bindings/
session.rs

1use crate::{
2    Adapter, Error, Wintun,
3    handle::{SafeEvent, UnsafeHandle},
4    packet, util, wintun_raw,
5};
6use std::{ptr, slice, sync::Arc, sync::OnceLock};
7use windows_sys::Win32::{
8    Foundation::{ERROR_NO_MORE_ITEMS, FALSE, GetLastError, HANDLE, WAIT_EVENT, WAIT_FAILED, WAIT_OBJECT_0},
9    System::Threading::{INFINITE, WaitForMultipleObjects},
10};
11
12/// Wrapper around a <https://git.zx2c4.com/wintun/about/#wintun_session_handle>
13pub struct Session {
14    /// The session handle given to us by WintunStartSession
15    pub(crate) inner: UnsafeHandle<wintun_raw::WINTUN_SESSION_HANDLE>,
16
17    /// Windows event handle that is signaled by the wintun driver when data becomes available to
18    /// read
19    pub(crate) read_event: OnceLock<UnsafeHandle<HANDLE>>,
20
21    /// Windows event handle that is signaled when [`Session::shutdown`] is called force blocking
22    /// readers to exit
23    pub(crate) shutdown_event: Arc<SafeEvent>,
24
25    /// The adapter that owns this session
26    pub(crate) adapter: Arc<Adapter>,
27}
28
29impl Session {
30    pub fn get_adapter(&self) -> Arc<Adapter> {
31        self.adapter.clone()
32    }
33
34    pub(crate) fn get_wintun(&self) -> Wintun {
35        self.adapter.wintun.clone()
36    }
37
38    /// Allocates a send packet of the specified size. Wraps WintunAllocateSendPacket
39    ///
40    /// All packets returned from this function must be sent using [`Session::send_packet`] because
41    /// wintun establishes the send packet order based on the invocation order of this function.
42    /// Therefore if a packet is allocated using this function, and then never sent, it will hold
43    /// up the send queue for all other packets allocated in the future. It is okay for the session
44    /// to shutdown with allocated packets that have not yet been sent
45    pub fn allocate_send_packet(self: &Arc<Self>, size: u16) -> Result<packet::Packet, Error> {
46        let wintun = self.get_wintun();
47        let ptr = unsafe { wintun.WintunAllocateSendPacket(self.inner.0, size as u32) };
48        if ptr.is_null() {
49            return Err(util::get_last_error()?.into());
50        }
51        Ok(packet::Packet {
52            //SAFETY: ptr is non null, aligned for u8, and readable for up to size bytes (which
53            //must be less than isize::MAX because bytes is a u16
54            bytes: unsafe { slice::from_raw_parts_mut(ptr, size as usize) },
55            session: self.clone(),
56            kind: packet::Kind::SendPacketPending,
57        })
58    }
59
60    /// Sends a packet previously allocated with [`Session::allocate_send_packet`]
61    pub fn send_packet(&self, mut packet: packet::Packet) {
62        assert!(matches!(packet.kind, packet::Kind::SendPacketPending));
63
64        let wintun = self.get_wintun();
65        unsafe { wintun.WintunSendPacket(self.inner.0, packet.bytes.as_ptr()) };
66        //Mark the packet at sent
67        packet.kind = packet::Kind::SendPacketSent;
68    }
69
70    /// Attempts to receive a packet from the virtual interface without blocking.
71    /// If there are no packets currently in the receive queue, this function returns Ok(None)
72    /// without blocking. If blocking until a packet is desirable, use [`Session::receive_blocking`]
73    pub fn try_receive(self: &Arc<Self>) -> Result<Option<packet::Packet>, Error> {
74        let mut size = 0u32;
75
76        let wintun = self.get_wintun();
77        let ptr = unsafe { wintun.WintunReceivePacket(self.inner.0, &mut size as *mut u32) };
78
79        debug_assert!(size <= u16::MAX as u32);
80        if ptr.is_null() {
81            //Wintun returns ERROR_NO_MORE_ITEMS instead of blocking if packets are not available
82            return match unsafe { GetLastError() } {
83                ERROR_NO_MORE_ITEMS => Ok(None),
84                e => Err(std::io::Error::from_raw_os_error(e as i32).into()),
85            };
86        }
87        Ok(Some(packet::Packet {
88            kind: packet::Kind::ReceivePacket,
89            //SAFETY: ptr is non null, aligned for u8, and readable for up to size bytes (which
90            //must be less than isize::MAX because bytes is a u16
91            bytes: unsafe { slice::from_raw_parts_mut(ptr, size as usize) },
92            session: self.clone(),
93        }))
94    }
95
96    /// # Safety
97    /// Returns the low level read event handle that is signaled when more data becomes available
98    /// to read
99    pub fn get_read_wait_event(&self) -> Result<UnsafeHandle<HANDLE>, Error> {
100        let wintun = self.get_wintun();
101        Ok(*self
102            .read_event
103            .get_or_init(|| UnsafeHandle(unsafe { wintun.WintunGetReadWaitEvent(self.inner.0) })))
104    }
105
106    pub fn get_shutdown_event(&self) -> UnsafeHandle<HANDLE> {
107        self.shutdown_event.get_handle()
108    }
109
110    /// Blocks until a packet is available, returning the next packet in the receive queue once this happens.
111    /// If the session is closed via [`Session::shutdown`] all threads currently blocking inside this function
112    /// will return Err(())
113    pub fn receive_blocking(self: &Arc<Self>) -> Result<packet::Packet, Error> {
114        loop {
115            //Try 5 times to receive without blocking so we don't have to issue a syscall to wait
116            //for the event if packets are being received at a rapid rate
117            for _ in 0..5 {
118                match self.try_receive() {
119                    Err(err) => return Err(err),
120                    Ok(Some(packet)) => return Ok(packet),
121                    Ok(None) => {
122                        //Try again
123                        continue;
124                    }
125                }
126            }
127            self.wait_read()?;
128        }
129    }
130
131    pub fn wait_read(&self) -> Result<(), Error> {
132        //Wait on both the read handle and the shutdown handle so that we stop when requested
133        let handles = [self.get_read_wait_event()?.0, self.shutdown_event.0.0];
134        let result = unsafe {
135            //SAFETY: We abide by the requirements of WaitForMultipleObjects, handles is a
136            //pointer to valid, aligned, stack memory
137            WaitForMultipleObjects(handles.len() as u32, &handles as _, FALSE, INFINITE)
138        };
139        const WAIT_OBJECT_1: WAIT_EVENT = WAIT_OBJECT_0 + 1;
140        match result {
141            WAIT_FAILED => Err(util::get_last_error()?.into()),
142            WAIT_OBJECT_0 => {
143                //We have data!
144                Ok(())
145            }
146            WAIT_OBJECT_1 => {
147                //Shutdown event triggered
148                Err(Error::ShuttingDown)
149            }
150            _ => {
151                //This should never happen
152                panic!("WaitForMultipleObjects returned unexpected value {:?}", result);
153            }
154        }
155    }
156
157    /// Cancels any active calls to [`Session::receive_blocking`] making them instantly return Err(_) so that session can be shutdown cleanly
158    pub fn shutdown(&self) -> Result<(), Error> {
159        self.shutdown_event.set_event()?;
160        Ok(())
161    }
162}
163
164impl Session {
165    pub fn try_recv(&self, buf: &mut [u8]) -> std::io::Result<usize> {
166        let mut size = 0u32;
167
168        let wintun = &self.adapter.wintun;
169        let ptr = unsafe { wintun.WintunReceivePacket(self.inner.0, &mut size as *mut u32) };
170
171        debug_assert!(size <= u16::MAX as u32);
172        if ptr.is_null() {
173            // Wintun returns ERROR_NO_MORE_ITEMS instead of blocking if packets are not available
174            return match unsafe { GetLastError() } {
175                ERROR_NO_MORE_ITEMS => Err(std::io::Error::from(std::io::ErrorKind::WouldBlock)),
176                e => Err(std::io::Error::from_raw_os_error(e as i32)),
177            };
178        }
179        let size = size as usize;
180        if size > buf.len() {
181            unsafe { wintun.WintunReleaseReceivePacket(self.inner.0, ptr) };
182            use std::io::{Error, ErrorKind::InvalidInput};
183            return Err(Error::new(InvalidInput, "destination buffer too small"));
184        }
185        unsafe { ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), size) };
186        unsafe { wintun.WintunReleaseReceivePacket(self.inner.0, ptr) };
187        Ok(size)
188    }
189
190    /// Blocks until a packet is available, returning the next packet in the receive queue once this happens.
191    /// If the session is closed via [`Session::shutdown`] all threads currently blocking inside this function
192    /// will return Err(())
193    pub fn recv(&self, buf: &mut [u8]) -> std::io::Result<usize> {
194        loop {
195            // Try 5 times to receive without blocking so we don't have to issue a syscall to wait
196            // for the event if packets are being received at a rapid rate
197            for _ in 0..5 {
198                return match self.try_recv(buf) {
199                    Ok(len) => Ok(len),
200                    Err(e) => {
201                        if e.kind() == std::io::ErrorKind::WouldBlock {
202                            // Try again
203                            continue;
204                        }
205                        Err(e)
206                    }
207                };
208            }
209            self.wait_read()?;
210        }
211    }
212
213    pub fn send(&self, buf: &[u8]) -> std::io::Result<usize> {
214        let wintun = &self.adapter.wintun;
215        let size = buf.len();
216        let ptr = unsafe { wintun.WintunAllocateSendPacket(self.inner.0, size as u32) };
217        if ptr.is_null() {
218            util::get_last_error()?;
219        }
220        unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), ptr, size) };
221        unsafe { wintun.WintunSendPacket(self.inner.0, ptr) };
222        Ok(buf.len())
223    }
224}
225
226impl Drop for Session {
227    fn drop(&mut self) {
228        if let Err(e) = self.shutdown() {
229            log::trace!("Failed to shutdown session: {}", e);
230        }
231        unsafe { self.get_wintun().WintunEndSession(self.inner.0) };
232        self.inner.0 = ptr::null_mut();
233    }
234}