summaryrefslogtreecommitdiffhomepage
path: root/src/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/windows.rs173
1 files changed, 150 insertions, 23 deletions
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
index 239449f..9c7875e 100644
--- a/src/platform/windows.rs
+++ b/src/platform/windows.rs
@@ -16,12 +16,9 @@
// along with DPIBreak. If not, see <https://www.gnu.org/licenses/>.
use anyhow::Result;
-use windivert::{
- WinDivert,
- layer::NetworkLayer,
- prelude
-};
-use std::sync::{LazyLock, Mutex};
+use windivert::{WinDivert, layer::NetworkLayer, prelude};
+use windivert::prelude::{WinDivertError, WinDivertRecvError, WinDivertShutdownMode};
+use std::sync::{Arc, LazyLock, Mutex, OnceLock};
use std::thread;
use crate::{opt, pkt};
use super::paexit;
@@ -33,6 +30,37 @@ pub fn pause() {
unsafe { _getch(); }
}
+static RECV_HANDLES: LazyLock<Mutex<Vec<Arc<WinDivert<NetworkLayer>>>>> =
+ LazyLock::new(|| Mutex::new(Vec::new()));
+
+fn open_recv_handle(filter: &str, flags: prelude::WinDivertFlags) -> Arc<WinDivert<NetworkLayer>> {
+ let h = Arc::new(open_handle(filter, flags));
+ RECV_HANDLES.lock().expect("mutex poisoned").push(h.clone());
+ h
+}
+
+fn shutdown_all() {
+ for h in RECV_HANDLES.lock().expect("mutex poisoned").iter() {
+ if let Err(e) = h.shutdown(WinDivertShutdownMode::Both) {
+ crate::warn!("windivert: shutdown: {e}");
+ }
+ }
+}
+
+fn cleanup_all() {
+ let handles: Vec<_> = RECV_HANDLES.lock().unwrap().drain(..).collect();
+ for h in handles {
+ match Arc::try_unwrap(h) {
+ Ok(mut wd) => {
+ _ = wd.close(windivert::CloseAction::Nothing);
+ },
+ Err(_still_shared) => {
+ crate::warn!("windivert: handle still referenced, skipping close");
+ }
+ }
+ }
+}
+
fn open_handle(filter: &str, flags: prelude::WinDivertFlags) -> WinDivert<NetworkLayer> {
use windivert::*;
@@ -43,7 +71,7 @@ fn open_handle(filter: &str, flags: prelude::WinDivertFlags) -> WinDivert<Networ
},
Err(e) => {
crate::error!("windivert: cannot open {filter}: {e}");
- paexit(1);
+ paexit(1);
}
};
h
@@ -54,15 +82,27 @@ pub fn bootstrap() -> Result<()> {
service_main();
}
+ install_ctrl_handler();
+
Ok(())
}
-static SEND_HANDLE: LazyLock<Mutex<WinDivert<NetworkLayer>>> = LazyLock::new(|| {
- let flags = prelude::WinDivertFlags::new()
- .set_send_only();
+static SEND_HANDLE: OnceLock<Mutex<WinDivert<NetworkLayer>>> = OnceLock::new();
+
+fn send_handle() -> &'static Mutex<WinDivert<NetworkLayer>> {
+ SEND_HANDLE.get_or_init(|| {
+ let flags = prelude::WinDivertFlags::new().set_send_only();
+ Mutex::new(open_handle("false", flags))
+ })
+}
- Mutex::new(open_handle("false", flags))
-});
+fn close_send_handle() {
+ if let Some(m) = SEND_HANDLE.get() && let Ok(mut wd) = m.lock() {
+ if let Err(e) = wd.close(windivert::CloseAction::Nothing) {
+ crate::warn!("windivert: close send handle: {e}");
+ }
+ }
+}
fn send_to_raw_1(pkt: &[u8]) -> Result<()> {
use windivert::*;
@@ -74,7 +114,7 @@ fn send_to_raw_1(pkt: &[u8]) -> Result<()> {
p.address.set_tcp_checksum(false); // For badsum; anyway it is already calculated
p.address.set_impostor(true); // to prevent inf loop
- SEND_HANDLE.lock().expect("mutex poisoned").send(&p)?;
+ send_handle().lock().expect("mutex poisoned").send(&p)?;
Ok(())
}
@@ -89,24 +129,102 @@ macro_rules! recv_loop {
loop {
match $handle.recv(Some(&mut buf)) {
Ok($pkt) => { $body }
+ // Check if it is shutdowned with WinDivertShutdown()
+ Err(WinDivertError::Recv(WinDivertRecvError::NoData)) => {
+ crate::info!("windivert: recv shutdown");
+ break;
+ }
Err(e) => { crate::warn!("windivert: recv: {}", e); }
}
}
};
}
+fn install_ctrl_handler() {
+ unsafe extern "system" {
+ fn SetConsoleCtrlHandler(
+ handler: Option<unsafe extern "system" fn(u32) -> i32>,
+ add: i32,
+ ) -> i32;
+ }
+
+ unsafe extern "system" fn sighandler(ctrl_type: u32) -> i32 {
+ // CTRL_C_EVENT=0, CTRL_BREAK_EVENT=1, CTRL_CLOSE_EVENT=2,
+ // CTRL_LOGOFF_EVENT=5, CTRL_SHUTDOWN_EVENT=6
+ match ctrl_type {
+ 0 | 1 | 5 | 6 => { shutdown_all(); 1 }
+ 2 => {
+ shutdown_all();
+
+ // When the user closes the console window by clicking the 'X' button,
+ // Windows terminates the process immediately after the thread ends;
+ // therefore, the program must wait at this point for `close_all` to
+ // execute.
+ loop { std::thread::sleep(std::time::Duration::from_millis(30)); }
+ }
+ _ => 0, // FALSE
+ }
+ }
+
+ let ok = unsafe { SetConsoleCtrlHandler(Some(sighandler), 1) };
+ if ok == 0 {
+ crate::warn!("SetConsoleCtrlHandler() failed");
+ }
+
+ crate::info!("cleanup handler installed");
+}
+
+/// Touch windivert service to avoid Error 1058 on WinDivertOpen()
+/// See https://github.com/basil00/WinDivert/issues/406
+fn touch_windivert() {
+ use windows::Win32::System::Services::*;
+ use windows::core::w;
+
+ unsafe {
+ match OpenSCManagerW(None, None, SC_MANAGER_CONNECT) {
+ Ok(scm) => {
+ match OpenServiceW(scm, w!("WinDivert"), SERVICE_QUERY_STATUS) {
+ Ok(svc) => {
+ crate::info!("Touched WinDivert service");
+
+ // I really dont know why, but just opening
+ // the handle is not enough. Performing the
+ // exact action executed by `sc query
+ // windivert` resolves the bad state issue
+ // described on the link above.
+ Let mut status = SERVICE_STATUS::default();
+ let q = QueryServiceStatus(svc, &mut status);
+ crate::debug!("OpenService ok, query={:?}, state={:?}",
+ q, status.dwCurrentState);
+ _ = CloseServiceHandle(svc);
+ }
+ Err(e) => {
+ crate::debug!("No service is good service. OpenService failed: {:?}", e);
+ }
+ }
+ _ = CloseServiceHandle(scm);
+ }
+ Err(e) => crate::debug!("OpenSCManager failed: {:?}", e),
+ }
+ }
+}
+
pub fn run() -> Result<()> {
+ touch_windivert();
+
let mut buf = Vec::<u8>::with_capacity(super::PACKET_SIZE_CAP);
- if opt::fake_autottl() {
- let handle = open_handle(
+ let sniff_thread = if opt::fake_autottl() {
+ let handle = open_recv_handle(
"!outbound and tcp and tcp.SrcPort == 443 and tcp.Syn and tcp.Ack",
prelude::WinDivertFlags::new().set_sniff()
);
- thread::spawn(move || { recv_loop!(handle, pkt => pkt::put_hop(&pkt.data)); });
- }
+ Some(thread::spawn(move || { recv_loop!(handle, pkt => pkt::put_hop(&pkt.data)); }))
+ } else {
+ None
+ };
- let divert = open_handle(
+ let divert = open_recv_handle(
concat!(
"outbound and tcp and tcp.DstPort == 443",
" ", "and tcp.Payload[0] == 22",
@@ -125,13 +243,24 @@ pub fn run() -> Result<()> {
rejected => send_to_raw_1(&pkt.data)?
)
});
+ drop(divert);
+ if let Some(jh) = sniff_thread && jh.join().is_err() {
+ crate::warn!("join for sniff thread failed: thread paniced");
+ }
+ cleanup_all();
+ close_send_handle();
+ if let Err(e) = windivert::WinDivert::uninstall() {
+ crate::warn!("windivert: uninstall failed: {e}");
+ }
+
+ Ok(())
}
fn service_run() {
use std::process::exit;
if run().is_err() {
- exit(1);
+ exit(1);
}
exit(0);
}
@@ -143,10 +272,8 @@ fn service_main() {
.can_stop()
.run(|_, command| {
match command {
- Command::Start => {
- std::thread::spawn(|| service_run());
- }
- Command::Stop => {}
+ Command::Start => { std::thread::spawn(|| service_run()); }
+ Command::Stop => { shutdown_all(); }
_ => {}
}
}) {