diff options
| author | David Lönnhager <david.l@mullvad.net> | 2020-10-13 14:40:53 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2021-07-02 09:54:19 +0200 |
| commit | 9cc3585e99c9ba798e8b2dab983b3aad96450180 (patch) | |
| tree | 6cdac39671831983649d3d49625682c9f8608830 | |
| parent | e5baa0e08816d535a031b3d8575701b8d43fb0c2 (diff) | |
| download | mullvadvpn-9cc3585e99c9ba798e8b2dab983b3aad96450180.tar.xz mullvadvpn-9cc3585e99c9ba798e8b2dab983b3aad96450180.zip | |
Log error events from split tunnel driver
| -rw-r--r-- | talpid-core/src/split_tunnel/windows/driver.rs | 199 | ||||
| -rw-r--r-- | talpid-core/src/split_tunnel/windows/mod.rs | 86 |
2 files changed, 265 insertions, 20 deletions
diff --git a/talpid-core/src/split_tunnel/windows/driver.rs b/talpid-core/src/split_tunnel/windows/driver.rs index 26495a5877..093012224b 100644 --- a/talpid-core/src/split_tunnel/windows/driver.rs +++ b/talpid-core/src/split_tunnel/windows/driver.rs @@ -11,7 +11,7 @@ use std::{ mem::{self, size_of}, net::{Ipv4Addr, Ipv6Addr}, os::windows::{ - ffi::OsStrExt, + ffi::{OsStrExt, OsStringExt}, fs::OpenOptionsExt, io::{AsRawHandle, RawHandle}, }, @@ -68,6 +68,43 @@ pub enum DriverState { Terminating = 5, } +#[repr(u32)] +#[derive(Clone, Copy)] +#[allow(dead_code)] +pub enum EventId { + StartSplittingProcess = 0, + StopSplittingProcess, + + // ErrorFlag = 0x80000000, + ErrorStartSplittingProcess = 0x80000001, + ErrorStopSplittingProcess, +} + +pub struct Event { + event_id: EventId, + body: EventBody, +} + +pub enum EventBody { + SplittingEvent { + process_id: u32, + reason: SplittingChangeReason, + image: OsString, + }, + SplittingError { + process_id: u32, + image: OsString, + }, +} + +#[repr(u32)] +#[derive(Debug)] +#[allow(dead_code)] +pub enum SplittingChangeReason { + ByInheritance = 0, + ByConfig = 1, +} + pub struct DeviceHandle { handle: fs::File, } @@ -226,6 +263,40 @@ impl DeviceHandle { Ok(()) } + + pub fn deque_event(&self, buffer: &mut Vec<u8>) -> io::Result<(EventId, EventBody)> { + deque_event(self.handle.as_raw_handle(), buffer) + } +} + +impl AsRawHandle for DeviceHandle { + fn as_raw_handle(&self) -> RawHandle { + self.handle.as_raw_handle() + } +} + +pub fn deque_event(handle: RawHandle, buffer: &mut Vec<u8>) -> io::Result<(EventId, EventBody)> { + device_io_control_buffer( + handle, + DriverIoctlCode::DequeEvent as u32, + None, + Some(buffer), + )?; + + let mut event_header: EventHeader = unsafe { mem::zeroed() }; + + unsafe { + ptr::copy_nonoverlapping( + &buffer[0], + &mut event_header as *mut _ as *mut u8, + mem::size_of_val(&event_header), + ) + }; + + Ok(( + event_header.event_id, + parse_event_buffer(&event_header, buffer), + )) } #[repr(C)] @@ -440,6 +511,91 @@ fn serialize_process_tree(processes: Vec<ProcessInfo>) -> Result<Vec<u8>, io::Er Ok(buffer) } +#[repr(C)] +struct EventHeader { + event_id: EventId, + event_size: usize, +} + +#[repr(C)] +struct SplittingEventHeader { + process_id: u32, + reason: SplittingChangeReason, + image_name_length: u16, +} + +#[repr(C)] +struct SplittingErrorEventHeader { + process_id: u32, + image_name_length: u16, +} + +fn parse_event_buffer(event_header: &EventHeader, buffer: &Vec<u8>) -> EventBody { + match event_header.event_id { + EventId::StartSplittingProcess | EventId::StopSplittingProcess => { + let mut event: SplittingEventHeader = unsafe { mem::zeroed() }; + unsafe { + ptr::copy_nonoverlapping( + &buffer[mem::size_of_val(event_header)], + &mut event as *mut _ as *mut u8, + mem::size_of_val(&event), + ) + }; + + let mut image_name = Vec::new(); + image_name.resize( + event.image_name_length as usize / mem::size_of::<u16>(), + 0u16, + ); + + unsafe { + ptr::copy_nonoverlapping( + &buffer[mem::size_of_val(event_header) + mem::size_of_val(&event)] as *const _ + as *const u16, + image_name.as_mut_ptr(), + image_name.len(), + ) + }; + + EventBody::SplittingEvent { + process_id: event.process_id, + reason: event.reason, + image: OsStringExt::from_wide(&image_name), + } + } + EventId::ErrorStartSplittingProcess | EventId::ErrorStopSplittingProcess => { + let mut event: SplittingErrorEventHeader = unsafe { mem::zeroed() }; + unsafe { + ptr::copy_nonoverlapping( + &buffer[mem::size_of_val(event_header)], + &mut event as *mut _ as *mut u8, + mem::size_of_val(&event), + ) + }; + + let mut image_name = Vec::new(); + image_name.resize( + event.image_name_length as usize / mem::size_of::<u16>(), + 0u16, + ); + + unsafe { + ptr::copy_nonoverlapping( + &buffer[mem::size_of_val(event_header) + mem::size_of_val(&event)] as *const _ + as *const u16, + image_name.as_mut_ptr(), + image_name.len(), + ) + }; + + EventBody::SplittingError { + process_id: event.process_id, + image: OsStringExt::from_wide(&image_name), + } + } + } +} + /// Send an IOCTL code to the given device handle. /// `input` specifies an optional buffer to send. /// Upon success, a buffer of size `output_size` is returned, or None if `output_size` is 0. @@ -449,22 +605,39 @@ pub fn device_io_control( input: Option<&[u8]>, output_size: u32, ) -> Result<Option<Vec<u8>>, io::Error> { - let input_ptr = match input { - Some(input) => input as *const _ as *mut _, - None => ptr::null_mut(), - }; - let input_len = input.map(|input| input.len()).unwrap_or(0); - let mut out_buffer = if output_size > 0 { Some(Vec::with_capacity(output_size as usize)) } else { None }; - let out_ptr = match out_buffer { - Some(ref mut out_buffer) => out_buffer.as_mut_ptr() as *mut _, + device_io_control_buffer(device, ioctl_code, input, out_buffer.as_mut()).map(|()| out_buffer) +} + +/// Send an IOCTL code to the given device handle. +/// `input` specifies an optional buffer to send. +/// Upon success, `output` buffer will contain at most `output.capacity()` bytes of data. +pub fn device_io_control_buffer( + device: RawHandle, + ioctl_code: u32, + input: Option<&[u8]>, + mut output: Option<&mut Vec<u8>>, +) -> Result<(), io::Error> { + let input_ptr = match input { + Some(input) => input as *const _ as *mut _, None => ptr::null_mut(), }; + let input_len = input.map(|input| input.len()).unwrap_or(0); + + let out_ptr = match output { + Some(ref mut output) => output.as_mut_ptr() as *mut _, + None => ptr::null_mut(), + }; + let output_size = if let Some(ref output) = output { + output.capacity() + } else { + 0 + }; let mut returned_bytes = 0u32; @@ -475,18 +648,18 @@ pub fn device_io_control( input_ptr, input_len as u32, out_ptr, - output_size, + output_size as u32, &mut returned_bytes as *mut _, ptr::null_mut(), // TODO ) }; - if let Some(ref mut out_buffer) = out_buffer { - unsafe { out_buffer.set_len(returned_bytes as usize) }; + if let Some(ref mut output) = output { + unsafe { output.set_len(returned_bytes as usize) }; } if result != 0 { - Ok(out_buffer) + Ok(()) } else { Err(io::Error::last_os_error()) } diff --git a/talpid-core/src/split_tunnel/windows/mod.rs b/talpid-core/src/split_tunnel/windows/mod.rs index c6b8fae332..857a20435d 100644 --- a/talpid-core/src/split_tunnel/windows/mod.rs +++ b/talpid-core/src/split_tunnel/windows/mod.rs @@ -5,8 +5,15 @@ use std::{ ffi::OsStr, io, net::{Ipv4Addr, Ipv6Addr}, + os::windows::{ + io::{AsRawHandle, IntoRawHandle, RawHandle}, + thread, + }, }; use talpid_types::ErrorExt; +use winapi::um::processthreadsapi::TerminateThread; + +const DRIVER_EVENT_BUFFER_SIZE: usize = 2048; /// Errors that may occur in [`SplitTunnel`]. #[derive(err_derive::Error, Debug)] @@ -26,22 +33,80 @@ pub enum Error { } /// Manages applications whose traffic to exclude from the tunnel. -pub struct SplitTunnel(driver::DeviceHandle); +pub struct SplitTunnel { + handle: driver::DeviceHandle, + event_thread: Option<std::thread::JoinHandle<()>>, +} + +struct HandleContainer { + handle: RawHandle, +} +// FIXME: ! This is not safe. The handle will be invalidated when SplitTunnel is dropped +unsafe impl Send for HandleContainer {} impl SplitTunnel { /// Initialize the driver. pub fn new() -> Result<Self, Error> { - Ok(SplitTunnel( - driver::DeviceHandle::new().map_err(Error::InitializationFailed)?, - )) + // TODO: spawn event monitor + let handle = driver::DeviceHandle::new().map_err(Error::InitializationFailed)?; + + // FIXME: Want to use same pointer, but must be certain that the thread dies after this dies + + let raw_handle = HandleContainer { + handle: handle.as_raw_handle(), + }; + + let event_thread = std::thread::spawn(move || { + use driver::{EventBody, EventId}; + + let mut data_buffer = Vec::with_capacity(DRIVER_EVENT_BUFFER_SIZE); + + loop { + match driver::deque_event(raw_handle.handle, &mut data_buffer) { + Ok((event_id, event_body)) => { + let event_str = match &event_id { + EventId::StartSplittingProcess + | EventId::ErrorStartSplittingProcess => "Start splitting process", + EventId::StopSplittingProcess | EventId::ErrorStopSplittingProcess => { + "Stop splitting process" + } + }; + + match event_body { + EventBody::SplittingError { process_id, image } => { + log::error!( + "FAILED: {}:\n\tpid: {}\n\timage: {:?}", + event_str, + process_id, + image, + ); + } + _ => (), + } + } + Err(error) => { + log::error!("{}", error.display_chain_with_msg("deque_event failed")); + } + } + + // TODO: Quit when signaled. Overlapping + WaitForMultipleObjects? + } + }); + + Ok(SplitTunnel { + handle, + event_thread: Some(event_thread), + }) } /// Set a list of applications to exclude from the tunnel. pub fn set_paths<T: AsRef<OsStr>>(&self, paths: &[T]) -> Result<(), Error> { if paths.len() > 0 { - self.0.set_config(paths).map_err(Error::SetConfiguration) + self.handle + .set_config(paths) + .map_err(Error::SetConfiguration) } else { - self.0.clear_config().map_err(Error::SetConfiguration) + self.handle.clear_config().map_err(Error::SetConfiguration) } } @@ -53,7 +118,7 @@ impl SplitTunnel { internet_ipv4: Ipv4Addr, internet_ipv6: Option<Ipv6Addr>, ) -> Result<(), Error> { - self.0 + self.handle .register_ips(tunnel_ipv4, tunnel_ipv6, internet_ipv4, internet_ipv6) .map_err(Error::RegisterIps) } @@ -61,6 +126,13 @@ impl SplitTunnel { impl Drop for SplitTunnel { fn drop(&mut self) { + // FIXME: Use signals to close the thread gracefully, followed by a join + if let Some(event_thread) = self.event_thread.take() { + unsafe { + TerminateThread(event_thread.into_raw_handle(), 0); + } + } + let paths: [&OsStr; 0] = []; if let Err(error) = self.set_paths(&paths) { log::error!("{}", error.display_chain()); |
