use crate::db::pool; use rusqlite::params; use serde::{Deserialize, Serialize}; use std::process::Command; use uuid::Uuid; // ── 数据结构 ────────────────────────────────────────────────────────────────── #[derive(Debug, Serialize, Deserialize, Clone)] pub struct MonitoredTool { pub id: String, pub name: String, pub exe_path: String, pub enabled: bool, } #[derive(Debug, Serialize, Clone)] pub struct ConnectionInfo { pub process_name: String, pub process_path: String, pub remote_address: String, pub remote_port: u16, } #[derive(Debug, Serialize, Clone)] pub struct ModeLogEntry { pub id: i64, pub mode: String, pub created_at: Option, } // ── 防火墙规则管理 ──────────────────────────────────────────────────────────── fn rule_display_name(tool_name: &str) -> String { format!("DevMgr_Block_{}", tool_name.replace(' ', "_")) } fn run_ps(script: &str) -> Result { let output = Command::new("powershell") .args(["-NonInteractive", "-NoProfile", "-Command", script]) .output() .map_err(|e| format!("无法启动 PowerShell: {e}"))?; if output.status.success() { Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } else { let err = String::from_utf8_lossy(&output.stderr).trim().to_string(); Err(err) } } /// 添加出站封锁规则 fn firewall_block(tool_name: &str, exe_path: &str) -> Result<(), String> { let name = rule_display_name(tool_name); // 先尝试删除旧规则(幂等) let _ = run_ps(&format!( "Remove-NetFirewallRule -DisplayName '{name}' -ErrorAction SilentlyContinue" )); run_ps(&format!( "New-NetFirewallRule -DisplayName '{name}' -Direction Outbound \ -Program '{exe_path}' -Action Block -Profile Any -ErrorAction Stop" )) .map(|_| ()) .map_err(|e| { if e.contains("access") || e.contains("denied") || e.contains("privilege") || e.contains("Privilege") { "需要管理员权限才能修改防火墙规则,请以管理员身份运行本程序".to_string() } else { e } }) } /// 删除出站封锁规则(恢复放行) fn firewall_unblock(tool_name: &str) -> Result<(), String> { let name = rule_display_name(tool_name); run_ps(&format!( "Remove-NetFirewallRule -DisplayName '{name}' -ErrorAction SilentlyContinue" )) .map(|_| ()) .map_err(|e| { if e.contains("access") || e.contains("denied") || e.contains("privilege") || e.contains("Privilege") { "需要管理员权限才能修改防火墙规则,请以管理员身份运行本程序".to_string() } else { e } }) } // ── Tauri 命令 ──────────────────────────────────────────────────────────────── /// 获取当前开发模式状态 #[tauri::command] pub fn get_dev_mode() -> Result { let conn = pool().get().map_err(|e| e.to_string())?; let val: Option = conn .query_row( "SELECT value FROM settings WHERE key = 'dev_mode'", [], |row| row.get(0), ) .ok(); // 默认为 false(非开发模式) Ok(val.as_deref() == Some("1")) } /// 切换开发模式(同时应用/撤销防火墙规则) #[tauri::command] pub fn set_dev_mode(enabled: bool) -> Result<(), String> { let conn = pool().get().map_err(|e| e.to_string())?; // 获取所有已启用的受监控工具 let mut stmt = conn .prepare("SELECT name, exe_path FROM network_monitored_tools WHERE enabled = 1") .map_err(|e| e.to_string())?; let tools: Vec<(String, String)> = stmt .query_map([], |row| Ok((row.get(0)?, row.get(1)?))) .map_err(|e| e.to_string())? .collect::, _>>() .map_err(|e| e.to_string())?; // 应用防火墙规则 let mut errors: Vec = Vec::new(); for (name, path) in &tools { let result = if enabled { firewall_unblock(name) } else { firewall_block(name, path) }; if let Err(e) = result { errors.push(format!("{name}: {e}")); break; // 权限错误无需继续尝试 } } if !errors.is_empty() { return Err(errors.join("\n")); } // 保存模式到设置 conn.execute( "INSERT INTO settings (key, value) VALUES ('dev_mode', ?1) ON CONFLICT(key) DO UPDATE SET value = excluded.value", params![if enabled { "1" } else { "0" }], ) .map_err(|e| e.to_string())?; // 记录切换历史 conn.execute( "INSERT INTO network_mode_log (mode, created_at) VALUES (?1, datetime('now'))", params![if enabled { "dev" } else { "non_dev" }], ) .map_err(|e| e.to_string())?; Ok(()) } /// 获取受监控工具列表 #[tauri::command] pub fn get_monitored_tools() -> Result, String> { let conn = pool().get().map_err(|e| e.to_string())?; let mut stmt = conn .prepare( "SELECT id, name, exe_path, enabled FROM network_monitored_tools ORDER BY name", ) .map_err(|e| e.to_string())?; let tools = stmt .query_map([], |row| { Ok(MonitoredTool { id: row.get(0)?, name: row.get(1)?, exe_path: row.get(2)?, enabled: row.get::<_, i64>(3)? == 1, }) }) .map_err(|e| e.to_string())? .collect::, _>>() .map_err(|e| e.to_string())?; Ok(tools) } /// 添加受监控工具 #[tauri::command] pub fn add_monitored_tool(name: String, exe_path: String) -> Result { let id = Uuid::new_v4().to_string(); let conn = pool().get().map_err(|e| e.to_string())?; conn.execute( "INSERT INTO network_monitored_tools (id, name, exe_path, enabled) VALUES (?1, ?2, ?3, 1)", params![id, name, exe_path], ) .map_err(|e| e.to_string())?; // 如果当前是非开发模式,立即对新工具应用封锁规则 let dev_mode: bool = conn .query_row("SELECT value FROM settings WHERE key = 'dev_mode'", [], |row| row.get::<_, String>(0)) .ok() .as_deref() == Some("1"); if !dev_mode { let _ = firewall_block(&name, &exe_path); // 非关键失败,忽略 } Ok(id) } /// 移除受监控工具(同时清除其防火墙规则) #[tauri::command] pub fn remove_monitored_tool(id: String) -> Result<(), String> { let conn = pool().get().map_err(|e| e.to_string())?; // 先查出名称,用于删除防火墙规则 let name: Option = conn .query_row( "SELECT name FROM network_monitored_tools WHERE id = ?1", params![id], |row| row.get(0), ) .ok(); conn.execute( "DELETE FROM network_monitored_tools WHERE id = ?1", params![id], ) .map_err(|e| e.to_string())?; // 清除对应防火墙规则(不管当前是什么模式都清理干净) if let Some(n) = name { let _ = firewall_unblock(&n); } Ok(()) } /// 获取当前活跃的出站网络连接(只返回受监控工具相关的进程) #[tauri::command] pub fn get_active_connections() -> Result, String> { // 获取受监控工具的进程名(exe 文件名,不含路径) let conn = pool().get().map_err(|e| e.to_string())?; let mut stmt = conn .prepare("SELECT exe_path FROM network_monitored_tools WHERE enabled = 1") .map_err(|e| e.to_string())?; let paths: Vec = stmt .query_map([], |row| row.get(0)) .map_err(|e| e.to_string())? .collect::, _>>() .map_err(|e| e.to_string())?; if paths.is_empty() { return Ok(vec![]); } // 提取文件名(不含后缀)用于匹配进程名 let process_names: Vec = paths .iter() .filter_map(|p| { std::path::Path::new(p) .file_stem() .map(|s| s.to_string_lossy().to_lowercase()) }) .collect(); // PowerShell 脚本:获取已建立的出站连接,每行 "进程名||远端IP|端口" // 不读取 MainModule.FileName(极慢,需枚举每个进程的模块列表,且对系统进程易超时) let ps_script = r#" $results = @() try { Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue | Where-Object { $_.RemoteAddress -notmatch '^(0\.0\.0\.0|127\.|::1$|::$)' } | ForEach-Object { $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue if ($proc) { $results += "$($proc.ProcessName)||$($_.RemoteAddress)|$($_.RemotePort)" } } } catch {} $results -join "`n" "#; let output = run_ps(ps_script).unwrap_or_default(); let mut connections: Vec = Vec::new(); for line in output.lines() { let parts: Vec<&str> = line.splitn(4, '|').collect(); if parts.len() < 4 { continue; } let proc_name = parts[0].to_lowercase(); // 只返回受监控工具的连接 if !process_names.iter().any(|n| proc_name.contains(n.as_str())) { continue; } let port: u16 = parts[3].parse().unwrap_or(0); connections.push(ConnectionInfo { process_name: parts[0].to_string(), process_path: parts[1].to_string(), remote_address: parts[2].to_string(), remote_port: port, }); } Ok(connections) } // ── WSL v2rayA 代理控制 ──────────────────────────────────────────────────────── fn run_wsl_root(cmd: &str) -> Result { std::process::Command::new("wsl") .args(["-u", "root", "-e", "bash", "-c", cmd]) .output() .map_err(|e| format!("WSL 不可用: {e}")) } /// 获取 v2rayA 运行状态:"running" | "stopped" | "wsl_unavailable" #[tauri::command] pub fn get_v2raya_status() -> Result { let output = run_wsl_root("pgrep -x v2raya > /dev/null 2>&1 && echo running || echo stopped") .map_err(|_| "wsl_unavailable".to_string())?; let out = String::from_utf8_lossy(&output.stdout).trim().to_string(); Ok(if out == "running" { "running".to_string() } else { "stopped".to_string() }) } /// 启动 v2rayA(后台运行,不阻塞 WSL 会话) #[tauri::command] pub fn start_v2raya() -> Result<(), String> { let output = run_wsl_root( "nohup v2raya --log-disable-color > /tmp/v2raya.log 2>&1 & sleep 1; \ pgrep -x v2raya > /dev/null && echo ok || echo fail", )?; let out = String::from_utf8_lossy(&output.stdout).trim().to_string(); if out.contains("ok") { Ok(()) } else { Err("v2rayA 启动失败,请确认已在 WSL 中安装(apt install v2raya)".to_string()) } } /// 停止 v2rayA #[tauri::command] pub fn stop_v2raya() -> Result<(), String> { run_wsl_root("pkill v2raya; exit 0")?; Ok(()) } /// 获取模式切换历史 #[tauri::command] pub fn get_mode_log(limit: Option) -> Result, String> { let conn = pool().get().map_err(|e| e.to_string())?; let n = limit.unwrap_or(20); let mut stmt = conn .prepare( "SELECT id, mode, created_at FROM network_mode_log ORDER BY created_at DESC LIMIT ?1", ) .map_err(|e| e.to_string())?; let rows = stmt .query_map(params![n], |row| { Ok(ModeLogEntry { id: row.get(0)?, mode: row.get(1)?, created_at: row.get(2)?, }) }) .map_err(|e| e.to_string())? .collect::, _>>() .map_err(|e| e.to_string())?; Ok(rows) }