diff options
| author | Odd Stranne <odd@mullvad.net> | 2018-11-13 19:55:04 +0100 |
|---|---|---|
| committer | Odd Stranne <odd@mullvad.net> | 2018-11-14 15:20:55 +0100 |
| commit | 4e70130ef0d684c276615a97fb7748dc4eb88e2b (patch) | |
| tree | b306967b573f2f864f03799dcb802c657df61f8d | |
| parent | 8a6bae399969ebd57be39ba502630120983fc995 (diff) | |
| download | mullvadvpn-4e70130ef0d684c276615a97fb7748dc4eb88e2b.tar.xz mullvadvpn-4e70130ef0d684c276615a97fb7748dc4eb88e2b.zip | |
Complement 'offline' module with code for Windows
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-core/src/offline/mod.rs | 6 | ||||
| -rw-r--r-- | talpid-core/src/offline/windows.rs | 190 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 3 |
5 files changed, 199 insertions, 2 deletions
diff --git a/Cargo.lock b/Cargo.lock index 5d127fb999..7e7c6ce73d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1705,6 +1705,7 @@ dependencies = [ "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "which 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "widestring 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 7dfe49219a..323c3026ab 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -44,6 +44,7 @@ system-configuration = "0.2" [target.'cfg(windows)'.dependencies] widestring = "0.3" winreg = "0.5" +winapi = { version = "0.3.6", features = ["handleapi", "libloaderapi", "synchapi", "winbase", "winuser"] } [dev-dependencies] tempfile = "3.0" diff --git a/talpid-core/src/offline/mod.rs b/talpid-core/src/offline/mod.rs index 696e7225b3..6cbbf04e9e 100644 --- a/talpid-core/src/offline/mod.rs +++ b/talpid-core/src/offline/mod.rs @@ -2,7 +2,11 @@ #[path = "macos.rs"] mod imp; -#[cfg(not(target_os = "macos"))] +#[cfg(target_os = "windows")] +#[path = "windows.rs"] +mod imp; + +#[cfg(not(any(windows, target_os = "macos")))] #[path = "dummy.rs"] mod imp; diff --git a/talpid-core/src/offline/windows.rs b/talpid-core/src/offline/windows.rs new file mode 100644 index 0000000000..47d5fdda9d --- /dev/null +++ b/talpid-core/src/offline/windows.rs @@ -0,0 +1,190 @@ +//! # License +//! +//! Copyright (C) 2018 Amagicom AB +//! +//! This program is free software: you can redistribute it and/or modify it under the terms of the +//! GNU General Public License as published by the Free Software Foundation, either version 3 of +//! the License, or (at your option) any later version. + +extern crate winapi; + +use self::winapi::shared::basetsd::LONG_PTR; +use self::winapi::shared::minwindef::{DWORD, LPARAM, LRESULT, UINT, WPARAM}; +use self::winapi::shared::windef::HWND; +use self::winapi::um::handleapi::CloseHandle; +use self::winapi::um::libloaderapi::GetModuleHandleW; +use self::winapi::um::processthreadsapi::GetThreadId; +use self::winapi::um::synchapi::WaitForSingleObject; +use self::winapi::um::winbase::INFINITE; +use self::winapi::um::winuser::{ + CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetMessageW, + GetWindowLongPtrW, PostQuitMessage, PostThreadMessageW, SetWindowLongPtrW, GWLP_USERDATA, + GWLP_WNDPROC, PBT_APMRESUMEAUTOMATIC, PBT_APMSUSPEND, WM_DESTROY, WM_POWERBROADCAST, WM_USER, +}; +use futures::sync::mpsc::UnboundedSender; +use log::debug; +use std::ffi::c_void; +use std::mem::zeroed; +use std::os::windows::io::IntoRawHandle; +use std::os::windows::io::RawHandle; +use std::ptr; +use std::thread; +use std::time::Duration; +use tunnel_state_machine::TunnelCommand; + +const CLASS_NAME: &[u8] = b"S\0T\0A\0T\0I\0C\0\0\0"; +const REQUEST_THREAD_SHUTDOWN: UINT = WM_USER + 1; + +error_chain!{ + errors { + ThreadCreationError { + description("Unable to create listener thread") + } + } +} + +pub struct BroadcastListener { + thread_handle: RawHandle, + thread_id: DWORD, +} + +unsafe impl Send for BroadcastListener {} + +impl BroadcastListener { + pub fn start<F>(client_callback: F) -> Result<Self> + where + F: Fn(UINT, WPARAM, LPARAM) + 'static + Send, + { + let join_handle = thread::Builder::new() + .spawn(move || unsafe { + Self::message_pump(client_callback); + }) + .chain_err(|| ErrorKind::ThreadCreationError)?; + + let real_handle = join_handle.into_raw_handle(); + + Ok(BroadcastListener { + thread_handle: real_handle, + thread_id: unsafe { GetThreadId(real_handle) }, + }) + } + + unsafe fn message_pump<F>(client_callback: F) + where + F: Fn(UINT, WPARAM, LPARAM), + { + let dummy_window = CreateWindowExW( + 0, + CLASS_NAME.as_ptr() as *const u16, + ptr::null_mut(), + 0, + 0, + 0, + 0, + 0, + ptr::null_mut(), + ptr::null_mut(), + GetModuleHandleW(ptr::null_mut()), + ptr::null_mut(), + ); + + // Move callback information to the heap. + // This enables us to reach the callback through a "thin pointer". + let boxed_callback = Box::new(client_callback); + + // Detach callback from Box. + let raw_callback = Box::into_raw(boxed_callback) as *mut c_void; + + SetWindowLongPtrW(dummy_window, GWLP_USERDATA, raw_callback as LONG_PTR); + SetWindowLongPtrW( + dummy_window, + GWLP_WNDPROC, + Self::window_procedure::<F> as LONG_PTR, + ); + + let mut msg = zeroed(); + + loop { + let status = GetMessageW(&mut msg, 0 as HWND, 0, 0); + + if status < 0 { + continue; + } + if status == 0 { + break; + } + + if msg.hwnd.is_null() { + if msg.message == REQUEST_THREAD_SHUTDOWN { + DestroyWindow(dummy_window); + } + } else { + DispatchMessageW(&mut msg); + } + } + + // Reattach callback to Box for proper clean-up. + let _ = Box::from_raw(raw_callback as *mut F); + } + + unsafe extern "system" fn window_procedure<F>( + window: HWND, + message: UINT, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT + where + F: Fn(UINT, WPARAM, LPARAM), + { + let raw_callback = GetWindowLongPtrW(window, GWLP_USERDATA); + + if raw_callback != 0 { + let typed_callback = &mut *(raw_callback as *mut F); + typed_callback(message, wparam, lparam); + } + + if message == WM_DESTROY { + PostQuitMessage(0); + return 0; + } + + DefWindowProcW(window, message, wparam, lparam) + } +} + +impl Drop for BroadcastListener { + fn drop(&mut self) { + unsafe { + PostThreadMessageW(self.thread_id, REQUEST_THREAD_SHUTDOWN, 0, 0); + WaitForSingleObject(self.thread_handle, INFINITE); + CloseHandle(self.thread_handle); + } + } +} + +pub fn spawn_monitor(sender: UnboundedSender<TunnelCommand>) -> Result<BroadcastListener> { + let listener = + BroadcastListener::start(move |message: UINT, wparam: WPARAM, _lparam: LPARAM| { + if message == WM_POWERBROADCAST { + if wparam == PBT_APMSUSPEND { + debug!("Machine is preparing to enter sleep mode"); + let _ = sender.unbounded_send(TunnelCommand::IsOffline(true)); + } else if wparam == PBT_APMRESUMEAUTOMATIC { + debug!("Machine is returning from sleep mode"); + let cloned_sender = sender.clone(); + thread::spawn(move || { + // TAP will be unavailable for approximately 2 seconds on a healthy machine. + thread::sleep(Duration::from_secs(2)); + debug!("TAP is presumed to have been re-initialized"); + let _ = cloned_sender.unbounded_send(TunnelCommand::IsOffline(false)); + }); + } + } + })?; + + Ok(listener) +} + +pub fn is_offline() -> bool { + false +} diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index d952e458f2..9f7f34b58b 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -55,7 +55,7 @@ where T: From<TunnelStateTransition> + Send + 'static, { let (command_tx, command_rx) = mpsc::unbounded(); - offline::spawn_monitor(command_tx.clone()) + let offline_monitor = offline::spawn_monitor(command_tx.clone()) .chain_err(|| "Unable to spawn offline state monitor")?; let is_offline = offline::is_offline(); @@ -89,6 +89,7 @@ where .expect("Failed to send startup error"); } } + std::mem::drop(offline_monitor); }); startup_result_rx |
