diff --git a/Cargo.lock b/Cargo.lock index 650bc7c..ce732db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1968,8 +1968,8 @@ dependencies = [ "url", "uuid", "wildmatch", + "windows 0.52.0", "windows-service", - "windows-sys 0.52.0", "winreg 0.52.0", "zerocopy", "zip", @@ -9061,6 +9061,16 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.58.0" diff --git a/easytier-gui/src-tauri/src/lib.rs b/easytier-gui/src-tauri/src/lib.rs index 5d5406c..e6d46e3 100644 --- a/easytier-gui/src-tauri/src/lib.rs +++ b/easytier-gui/src-tauri/src/lib.rs @@ -89,6 +89,7 @@ fn get_os_hostname() -> Result { #[tauri::command] fn set_logging_level(level: String) -> Result<(), String> { + #[allow(static_mut_refs)] let sender = unsafe { LOGGER_LEVEL_SENDER.as_ref().unwrap() }; sender.send(level).map_err(|e| e.to_string())?; Ok(()) @@ -188,7 +189,10 @@ pub fn run() { let Ok(Some(logger_reinit)) = utils::init_logger(config, true) else { return Ok(()); }; - unsafe { LOGGER_LEVEL_SENDER.replace(logger_reinit) }; + #[allow(static_mut_refs)] + unsafe { + LOGGER_LEVEL_SENDER.replace(logger_reinit) + }; // for tray icon, menu need to be built in js #[cfg(not(target_os = "android"))] diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 5c7abf7..cfdda20 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -204,12 +204,15 @@ netlink-packet-core = { version = "0.7.0" } netlink-packet-utils = "0.5.2" [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.52", features = [ - "Win32_Networking_WinSock", - "Win32_NetworkManagement_IpHelper", +windows = { version = "0.52.0", features = [ "Win32_Foundation", + "Win32_NetworkManagement_WindowsFirewall", + "Win32_System_Com", + "Win32_Networking", + "Win32_System_Ole", + "Win32_Networking_WinSock", "Win32_System_IO", -] } +]} encoding = "0.2" winreg = "0.52" windows-service = "0.7.0" diff --git a/easytier/src/arch/windows.rs b/easytier/src/arch/windows.rs index cf631ba..904de87 100644 --- a/easytier/src/arch/windows.rs +++ b/easytier/src/arch/windows.rs @@ -1,26 +1,27 @@ -use std::{ - ffi::c_void, - io::{self, ErrorKind}, - mem, - net::SocketAddr, - os::windows::io::AsRawSocket, - ptr, -}; +use std::{io, net::SocketAddr, os::windows::io::AsRawSocket}; +use anyhow::Context; use network_interface::NetworkInterfaceConfig; -use windows_sys::{ - core::PCSTR, +use windows::{ + core::BSTR, Win32::{ Foundation::{BOOL, FALSE}, + NetworkManagement::WindowsFirewall::{ + INetFwPolicy2, INetFwRule, NET_FW_ACTION_ALLOW, NET_FW_PROFILE2_PRIVATE, + NET_FW_PROFILE2_PUBLIC, NET_FW_RULE_DIR_IN, NET_FW_RULE_DIR_OUT, + }, Networking::WinSock::{ htonl, setsockopt, WSAGetLastError, WSAIoctl, IPPROTO_IP, IPPROTO_IPV6, IPV6_UNICAST_IF, IP_UNICAST_IF, SIO_UDP_CONNRESET, SOCKET, SOCKET_ERROR, }, + System::Com::{ + CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_MULTITHREADED, + }, }, }; pub fn disable_connection_reset(socket: &S) -> io::Result<()> { - let handle = socket.as_raw_socket() as SOCKET; + let handle = SOCKET(socket.as_raw_socket() as usize); unsafe { // Ignoring UdpSocket's WSAECONNRESET error @@ -39,21 +40,18 @@ pub fn disable_connection_reset(socket: &S) -> io::Result<()> { let ret = WSAIoctl( handle, SIO_UDP_CONNRESET, - &enable as *const _ as *const c_void, - mem::size_of_val(&enable) as u32, - ptr::null_mut(), + Some(&enable as *const _ as *const std::ffi::c_void), + std::mem::size_of_val(&enable) as u32, + None, 0, &mut bytes_returned as *mut _, - ptr::null_mut(), + None, None, ); if ret == SOCKET_ERROR { - use std::io::Error; - - // Error occurs let err_code = WSAGetLastError(); - return Err(Error::from_raw_os_error(err_code)); + return Err(std::io::Error::from_raw_os_error(err_code.0)); } } @@ -63,7 +61,7 @@ pub fn disable_connection_reset(socket: &S) -> io::Result<()> { pub fn interface_count() -> io::Result { let ifaces = network_interface::NetworkInterface::show().map_err(|e| { io::Error::new( - ErrorKind::NotFound, + io::ErrorKind::NotFound, format!("Failed to get interfaces. error: {}", e), ) })?; @@ -73,7 +71,7 @@ pub fn interface_count() -> io::Result { pub fn find_interface_index(iface_name: &str) -> io::Result { let ifaces = network_interface::NetworkInterface::show().map_err(|e| { io::Error::new( - ErrorKind::NotFound, + io::ErrorKind::NotFound, format!("Failed to get interfaces. {}, error: {}", iface_name, e), ) })?; @@ -82,7 +80,7 @@ pub fn find_interface_index(iface_name: &str) -> io::Result { } tracing::error!("Failed to find interface index for {}", iface_name); Err(io::Error::new( - ErrorKind::NotFound, + io::ErrorKind::NotFound, format!("{}", iface_name), )) } @@ -92,7 +90,7 @@ pub fn set_ip_unicast_if( addr: &SocketAddr, iface: &str, ) -> io::Result<()> { - let handle = socket.as_raw_socket() as SOCKET; + let handle = SOCKET(socket.as_raw_socket() as usize); let if_index = find_interface_index(iface)?; @@ -100,30 +98,23 @@ pub fn set_ip_unicast_if( // https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options let ret = match addr { SocketAddr::V4(..) => { - // Interface index is in network byte order for IPPROTO_IP. let if_index = htonl(if_index); - setsockopt( - handle, - IPPROTO_IP as i32, - IP_UNICAST_IF as i32, - &if_index as *const _ as PCSTR, - mem::size_of_val(&if_index) as i32, - ) + let if_index_bytes = if_index.to_ne_bytes(); + setsockopt(handle, IPPROTO_IP.0, IP_UNICAST_IF, Some(&if_index_bytes)) } SocketAddr::V6(..) => { - // Interface index is in host byte order for IPPROTO_IPV6. + let if_index_bytes = if_index.to_ne_bytes(); setsockopt( handle, - IPPROTO_IPV6 as i32, - IPV6_UNICAST_IF as i32, - &if_index as *const _ as PCSTR, - mem::size_of_val(&if_index) as i32, + IPPROTO_IPV6.0, + IPV6_UNICAST_IF, + Some(&if_index_bytes), ) } }; if ret == SOCKET_ERROR { - let err = io::Error::from_raw_os_error(WSAGetLastError()); + let err = std::io::Error::from_raw_os_error(WSAGetLastError().0); tracing::error!( "set IP_UNICAST_IF / IPV6_UNICAST_IF interface: {}, index: {}, error: {}", iface, @@ -152,4 +143,95 @@ pub fn setup_socket_for_win( } Ok(()) -} \ No newline at end of file +} + +struct ComInitializer; + +impl ComInitializer { + fn new() -> windows::core::Result { + unsafe { CoInitializeEx(None, COINIT_MULTITHREADED)? }; + Ok(Self) + } +} + +impl Drop for ComInitializer { + fn drop(&mut self) { + unsafe { + CoUninitialize(); + } + } +} + +pub fn do_add_self_to_firewall_allowlist(inbound: bool) -> anyhow::Result<()> { + let _com = ComInitializer::new()?; + // 创建防火墙策略实例 + let policy: INetFwPolicy2 = unsafe { + CoCreateInstance( + &windows::Win32::NetworkManagement::WindowsFirewall::NetFwPolicy2, + None, + CLSCTX_ALL, + ) + }?; + + // 创建防火墙规则实例 + let rule: INetFwRule = unsafe { + CoCreateInstance( + &windows::Win32::NetworkManagement::WindowsFirewall::NetFwRule, + None, + CLSCTX_ALL, + ) + }?; + + // 设置规则属性 + let exe_path = std::env::current_exe() + .with_context(|| "Failed to get current executable path when adding firewall rule")? + .to_string_lossy() + .replace(r"\\?\", ""); + + let name = BSTR::from(format!( + "EasyTier {} ({})", + exe_path, + if inbound { "Inbound" } else { "Outbound" } + )); + let desc = BSTR::from("Allow EasyTier to do subnet proxy and kcp proxy"); + let app_path = BSTR::from(&exe_path); + + unsafe { + rule.SetName(&name)?; + rule.SetDescription(&desc)?; + rule.SetApplicationName(&app_path)?; + rule.SetAction(NET_FW_ACTION_ALLOW)?; + if inbound { + rule.SetDirection(NET_FW_RULE_DIR_IN)?; // 允许入站连接 + } else { + rule.SetDirection(NET_FW_RULE_DIR_OUT)?; // 允许出站连接 + } + rule.SetEnabled(windows::Win32::Foundation::VARIANT_TRUE)?; + rule.SetProfiles(NET_FW_PROFILE2_PRIVATE.0 | NET_FW_PROFILE2_PUBLIC.0)?; + rule.SetGrouping(&BSTR::from("EasyTier"))?; + + // 获取规则集合并添加新规则 + let rules = policy.Rules()?; + rules.Remove(&name)?; // 先删除同名规则 + rules.Add(&rule)?; + } + + Ok(()) +} + +pub fn add_self_to_firewall_allowlist() -> anyhow::Result<()> { + do_add_self_to_firewall_allowlist(true)?; + do_add_self_to_firewall_allowlist(false)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_self_to_firewall_allowlist() { + let res = add_self_to_firewall_allowlist(); + assert!(res.is_ok()); + } +} diff --git a/easytier/src/gateway/tcp_proxy.rs b/easytier/src/gateway/tcp_proxy.rs index 04c188d..3f88afb 100644 --- a/easytier/src/gateway/tcp_proxy.rs +++ b/easytier/src/gateway/tcp_proxy.rs @@ -452,7 +452,10 @@ impl TcpProxy { async fn get_proxy_listener(&self) -> Result { #[cfg(feature = "smoltcp")] - if self.global_ctx.get_flags().use_smoltcp || self.global_ctx.no_tun() { + if self.global_ctx.get_flags().use_smoltcp + || self.global_ctx.no_tun() + || cfg!(target_os = "android") + { // use smoltcp network stack self.local_port .store(8899, std::sync::atomic::Ordering::Relaxed); diff --git a/easytier/src/instance/virtual_nic.rs b/easytier/src/instance/virtual_nic.rs index d9e1c6c..eda8acf 100644 --- a/easytier/src/instance/virtual_nic.rs +++ b/easytier/src/instance/virtual_nic.rs @@ -349,6 +349,15 @@ impl VirtualNic { { let dev_name = self.global_ctx.get_flags().dev_name; + match crate::arch::windows::add_self_to_firewall_allowlist() { + Ok(_) => tracing::info!("add_self_to_firewall_allowlist successful!"), + Err(e) => { + println!("Failed to add Easytier to firewall allowlist, Subnet proxy and KCP proxy may not work properly. error: {}", e); + println!("You can add firewall rules manually, or use --use-smoltcp to run with user-space TCP/IP stack."); + println!(""); + } + } + match checkreg(&dev_name) { Ok(_) => tracing::trace!("delete successful!"), Err(e) => tracing::error!("An error occurred: {}", e),