From d0836781d1dbd42b7106ea725565687027e1267c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=80=90=E9=9B=81=E5=8D=97=E9=A3=9B?= Date: Wed, 16 Apr 2025 10:22:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=90=86=E4=B8=BB?= =?UTF-8?q?=E6=9C=BA=E7=9A=84=E8=AE=BE=E7=BD=AE=EF=BC=8C=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E8=AE=BE=E7=BD=AE=E4=B8=BA=E5=85=B6=E4=BB=96?= =?UTF-8?q?IP=EF=BC=88=E9=9D=9E127.0.0.1=EF=BC=89=20(#2963)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 允许下拉选择ip地址(支持IPv6)、localhost、以及当前系统的主机名,同时兼容手工输入 --- src-tauri/Cargo.lock | 13 +- src-tauri/Cargo.toml | 1 + src-tauri/src/cmd/network.rs | 19 +++ src-tauri/src/config/verge.rs | 8 +- src-tauri/src/core/sysopt.rs | 22 +-- src-tauri/src/feat/proxy.rs | 11 +- src-tauri/src/lib.rs | 1 + .../setting/mods/sysproxy-viewer.tsx | 150 +++++++++++++++++- src/locales/ar.json | 2 + src/locales/en.json | 2 + src/locales/fa.json | 2 + src/locales/id.json | 2 + src/locales/ru.json | 2 + src/locales/tt.json | 2 + src/locales/zh.json | 2 + src/services/cmds.ts | 4 + src/services/types.d.ts | 1 + 17 files changed, 223 insertions(+), 21 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index bd306bdd..26decde9 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1056,6 +1056,7 @@ dependencies = [ "dirs 6.0.0", "dunce", "futures", + "gethostname 1.0.1", "getrandom 0.3.2", "image", "imageproc", @@ -2416,6 +2417,16 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "gethostname" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7131e57abbde63513e0e6636f76668a1ca9798dcae2df4e283cae9ee83859e" +dependencies = [ + "rustix 1.0.3", + "windows-targets 0.52.6", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -9319,7 +9330,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ - "gethostname", + "gethostname 0.4.3", "rustix 0.38.44", "x11rb-protocol", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0b96e80b..4497e0d6 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -75,6 +75,7 @@ mihomo_api = { path = "src_crates/crate_mihomo_api" } ab_glyph = "0.2.29" tungstenite = "0.26.2" libc = "0.2" +gethostname = "1.0.1" [target.'cfg(windows)'.dependencies] runas = "=1.2.0" diff --git a/src-tauri/src/cmd/network.rs b/src-tauri/src/cmd/network.rs index 5bc17163..ec2dabbf 100644 --- a/src-tauri/src/cmd/network.rs +++ b/src-tauri/src/cmd/network.rs @@ -31,6 +31,25 @@ pub fn get_auto_proxy() -> CmdResult { Ok(map) } +/// 获取系统主机名 +#[tauri::command] +pub fn get_system_hostname() -> CmdResult { + use gethostname::gethostname; + + // 获取系统主机名,处理可能的非UTF-8字符 + let hostname = match gethostname().into_string() { + Ok(name) => name, + Err(os_string) => { + // 对于包含非UTF-8的主机名,使用调试格式化 + let fallback = format!("{:?}", os_string); + // 去掉可能存在的引号 + fallback.trim_matches('"').to_string() + } + }; + + Ok(hostname) +} + /// 获取网络接口列表 #[tauri::command] pub fn get_network_interfaces() -> Vec { diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index 2aa8430a..79f0ab08 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -88,6 +88,9 @@ pub struct IVerge { /// pac script content pub pac_file_content: Option, + /// proxy host address + pub proxy_host: Option, + /// theme setting pub theme_setting: Option, @@ -275,6 +278,7 @@ impl IVerge { enable_system_proxy: Some(false), proxy_auto_config: Some(false), pac_file_content: Some(DEFAULT_PAC.into()), + proxy_host: Some("127.0.0.1".into()), enable_random_port: Some(false), #[cfg(not(target_os = "windows"))] verge_redir_port: Some(7895), @@ -368,7 +372,7 @@ impl IVerge { patch!(proxy_guard_duration); patch!(proxy_auto_config); patch!(pac_file_content); - + patch!(proxy_host); patch!(theme_setting); patch!(web_ui_list); patch!(clash_core); @@ -452,6 +456,7 @@ pub struct IVergeResponse { pub proxy_guard_duration: Option, pub proxy_auto_config: Option, pub pac_file_content: Option, + pub proxy_host: Option, pub theme_setting: Option, pub web_ui_list: Option>, pub clash_core: Option, @@ -520,6 +525,7 @@ impl From for IVergeResponse { proxy_guard_duration: verge.proxy_guard_duration, proxy_auto_config: verge.proxy_auto_config, pac_file_content: verge.pac_file_content, + proxy_host: verge.proxy_host, theme_setting: verge.theme_setting, web_ui_list: verge.web_ui_list, clash_core: verge.clash_core, diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index 9f31a472..173535f6 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -76,12 +76,13 @@ impl Sysopt { .unwrap_or(Config::clash().data().get_mixed_port()); let pac_port = IVerge::get_singleton_port(); - let (sys_enable, pac_enable) = { + let (sys_enable, pac_enable, proxy_host) = { let verge = Config::verge(); let verge = verge.latest(); ( verge.enable_system_proxy.unwrap_or(false), verge.proxy_auto_config.unwrap_or(false), + verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")), ) }; @@ -89,13 +90,13 @@ impl Sysopt { { let mut sys = Sysproxy { enable: false, - host: String::from("127.0.0.1"), + host: proxy_host.clone(), port, bypass: get_bypass(), }; let mut auto = Autoproxy { enable: false, - url: format!("http://127.0.0.1:{pac_port}/commands/pac"), + url: format!("http://{}:{}/commands/pac", proxy_host, pac_port), }; if !sys_enable { @@ -139,7 +140,7 @@ impl Sysopt { let shell = app_handle.shell(); let output = if pac_enable { - let address = format!("http://{}:{}/commands/pac", "127.0.0.1", pac_port); + let address = format!("http://{}:{}/commands/pac", proxy_host, pac_port); let output = shell .command(sysproxy_exe.as_path().to_str().unwrap()) .args(["pac", address.as_str()]) @@ -148,7 +149,7 @@ impl Sysopt { .unwrap(); output } else { - let address = format!("{}:{}", "127.0.0.1", port); + let address = format!("{}:{}", proxy_host, port); let bypass = get_bypass(); let output = shell .command(sysproxy_exe.as_path().to_str().unwrap()) @@ -256,7 +257,7 @@ impl Sysopt { loop { sleep(Duration::from_secs(wait_secs)).await; - let (enable, guard, guard_duration, pac) = { + let (enable, guard, guard_duration, pac, proxy_host) = { let verge = Config::verge(); let verge = verge.latest(); ( @@ -264,6 +265,7 @@ impl Sysopt { verge.enable_proxy_guard.unwrap_or(false), verge.proxy_guard_duration.unwrap_or(10), verge.proxy_auto_config.unwrap_or(false), + verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")), ) }; @@ -303,13 +305,13 @@ impl Sysopt { if pac { let autoproxy = Autoproxy { enable: true, - url: format!("http://127.0.0.1:{pac_port}/commands/pac"), + url: format!("http://{}:{}/commands/pac", proxy_host, pac_port), }; logging_error!(Type::System, true, autoproxy.set_auto_proxy()); } else { let sysproxy = Sysproxy { enable: true, - host: "127.0.0.1".into(), + host: proxy_host.into(), port, bypass: get_bypass(), }; @@ -333,7 +335,7 @@ impl Sysopt { let shell = app_handle.shell(); let output = if pac { - let address = format!("http://{}:{}/commands/pac", "127.0.0.1", pac_port); + let address = format!("http://{}:{}/commands/pac", proxy_host, pac_port); shell .command(sysproxy_exe.as_path().to_str().unwrap()) @@ -342,7 +344,7 @@ impl Sysopt { .await .unwrap() } else { - let address = format!("{}:{}", "127.0.0.1", port); + let address = format!("{}:{}", proxy_host, port); let bypass = get_bypass(); shell diff --git a/src-tauri/src/feat/proxy.rs b/src-tauri/src/feat/proxy.rs index 5e24c3a0..a7f22f09 100644 --- a/src-tauri/src/feat/proxy.rs +++ b/src-tauri/src/feat/proxy.rs @@ -63,9 +63,14 @@ pub fn toggle_tun_mode(not_save_file: Option) { /// Copy proxy environment variables to clipboard pub fn copy_clash_env() { - // 从环境变量获取IP地址,默认127.0.0.1 - let clash_verge_rev_ip = - env::var("CLASH_VERGE_REV_IP").unwrap_or_else(|_| "127.0.0.1".to_string()); + // 从环境变量获取IP地址,如果没有则从配置中获取 proxy_host,默认为 127.0.0.1 + let clash_verge_rev_ip = env::var("CLASH_VERGE_REV_IP").unwrap_or_else(|_| { + Config::verge() + .latest() + .proxy_host + .clone() + .unwrap_or_else(|| "127.0.0.1".to_string()) + }); let app_handle = handle::Handle::global().app_handle().unwrap(); let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7897) }; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 33acf0a4..e30a7aa5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -156,6 +156,7 @@ pub fn run() { cmd::open_core_dir, cmd::get_portable_flag, cmd::get_network_interfaces, + cmd::get_system_hostname, cmd::restart_core, cmd::restart_app, // 添加新的命令 diff --git a/src/components/setting/mods/sysproxy-viewer.tsx b/src/components/setting/mods/sysproxy-viewer.tsx index b4063cf6..b8c44514 100644 --- a/src/components/setting/mods/sysproxy-viewer.tsx +++ b/src/components/setting/mods/sysproxy-viewer.tsx @@ -3,10 +3,17 @@ import { BaseFieldset } from "@/components/base/base-fieldset"; import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { EditorViewer } from "@/components/profile/editor-viewer"; import { useVerge } from "@/hooks/use-verge"; -import { getAutotemProxy, getSystemProxy } from "@/services/cmds"; +import { + getAutotemProxy, + getNetworkInterfaces, + getNetworkInterfacesInfo, + getSystemHostname, + getSystemProxy, +} from "@/services/cmds"; import getSystem from "@/utils/get-system"; import { EditRounded } from "@mui/icons-material"; import { + Autocomplete, Button, InputAdornment, List, @@ -17,10 +24,16 @@ import { Typography, } from "@mui/material"; import { useLockFn } from "ahooks"; -import { forwardRef, useImperativeHandle, useMemo, useState } from "react"; +import { + forwardRef, + useImperativeHandle, + useEffect, + useMemo, + useState, +} from "react"; import { useTranslation } from "react-i18next"; const DEFAULT_PAC = `function FindProxyForURL(url, host) { - return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;"; + return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;"; }`; /** NO_PROXY validation */ @@ -64,6 +77,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { const [open, setOpen] = useState(false); const [editorOpen, setEditorOpen] = useState(false); const { verge, patchVerge } = useVerge(); + const [hostOptions, setHostOptions] = useState([]); type SysProxy = Awaited>; const [sysproxy, setSysproxy] = useState(); @@ -79,6 +93,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { use_default_bypass, system_proxy_bypass, proxy_guard_duration, + proxy_host, } = verge ?? {}; const [value, setValue] = useState({ @@ -88,6 +103,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { use_default: use_default_bypass ?? true, pac: proxy_auto_config, pac_content: pac_file_content ?? DEFAULT_PAC, + proxy_host: proxy_host ?? "127.0.0.1", }); const defaultBypass = () => { @@ -110,13 +126,74 @@ export const SysproxyViewer = forwardRef((props, ref) => { use_default: use_default_bypass ?? true, pac: proxy_auto_config, pac_content: pac_file_content ?? DEFAULT_PAC, + proxy_host: proxy_host ?? "127.0.0.1", }); getSystemProxy().then((p) => setSysproxy(p)); getAutotemProxy().then((p) => setAutoproxy(p)); + fetchNetworkInterfaces(); }, close: () => setOpen(false), })); + // 获取网络接口和主机名 + const fetchNetworkInterfaces = async () => { + try { + // 获取系统网络接口信息 + const interfaces = await getNetworkInterfacesInfo(); + const ipAddresses: string[] = []; + + // 从interfaces中提取IPv4和IPv6地址 + interfaces.forEach((iface) => { + iface.addr.forEach((address) => { + if (address.V4 && address.V4.ip) { + ipAddresses.push(address.V4.ip); + } + if (address.V6 && address.V6.ip) { + ipAddresses.push(address.V6.ip); + } + }); + }); + + // 获取当前系统的主机名 + let hostname = ""; + try { + hostname = await getSystemHostname(); + console.log("获取到主机名:", hostname); + } catch (err) { + console.error("获取主机名失败:", err); + } + + // 构建选项列表 + const options = ["127.0.0.1", "localhost"]; + + // 确保主机名添加到列表,即使它是空字符串也记录下来 + if (hostname) { + // 如果主机名不是localhost或127.0.0.1,则添加它 + if (hostname !== "localhost" && hostname !== "127.0.0.1") { + hostname = hostname + ".local"; + options.push(hostname); + console.log("主机名已添加到选项中:", hostname); + } else { + console.log("主机名与已有选项重复:", hostname); + } + } else { + console.log("主机名为空"); + } + + // 添加IP地址 + options.push(...ipAddresses); + + // 去重 + const uniqueOptions = Array.from(new Set(options)); + console.log("最终选项列表:", uniqueOptions); + setHostOptions(uniqueOptions); + } catch (error) { + console.error("获取网络接口失败:", error); + // 失败时至少提供基本选项 + setHostOptions(["127.0.0.1", "localhost"]); + } + }; + const onSave = useLockFn(async () => { if (value.duration < 1) { Notice.error(t("Proxy Daemon Duration Cannot be Less than 1 Second")); @@ -127,6 +204,23 @@ export const SysproxyViewer = forwardRef((props, ref) => { return; } + // 修改验证规则,允许IP和主机名 + const ipv4Regex = + /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + const ipv6Regex = + /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; + const hostnameRegex = + /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; + + if ( + !ipv4Regex.test(value.proxy_host) && + !ipv6Regex.test(value.proxy_host) && + !hostnameRegex.test(value.proxy_host) + ) { + Notice.error(t("Invalid Proxy Host Format")); + return; + } + const patch: Partial = {}; if (value.guard !== enable_proxy_guard) { @@ -138,15 +232,34 @@ export const SysproxyViewer = forwardRef((props, ref) => { if (value.bypass !== system_proxy_bypass) { patch.system_proxy_bypass = value.bypass; } - if (value.pac !== proxy_auto_config) { patch.proxy_auto_config = value.pac; } if (value.use_default !== use_default_bypass) { patch.use_default_bypass = value.use_default; } - if (value.pac_content !== pac_file_content) { - patch.pac_file_content = value.pac_content; + + let pacContent = value.pac_content; + if (pacContent) { + pacContent = pacContent.replace(/%proxy_host%/g, value.proxy_host); + } + + if (pacContent !== pac_file_content) { + patch.pac_file_content = pacContent; + } + + // 处理IPv6地址,如果是IPv6地址但没有被方括号包围,则添加方括号 + let proxyHost = value.proxy_host; + if ( + ipv6Regex.test(proxyHost) && + !proxyHost.startsWith("[") && + !proxyHost.endsWith("]") + ) { + proxyHost = `[${proxyHost}]`; + } + + if (proxyHost !== proxy_host) { + patch.proxy_host = proxyHost; } try { @@ -199,6 +312,31 @@ export const SysproxyViewer = forwardRef((props, ref) => { )} + + + ( + + )} + onChange={(_, newValue) => { + setValue((v) => ({ + ...v, + proxy_host: newValue || "127.0.0.1", + })); + }} + onInputChange={(_, newInputValue) => { + setValue((v) => ({ + ...v, + proxy_host: newInputValue || "127.0.0.1", + })); + }} + /> + ("get_network_interfaces"); } +export async function getSystemHostname() { + return invoke("get_system_hostname"); +} + export async function getNetworkInterfacesInfo() { return invoke("get_network_interfaces_info"); } diff --git a/src/services/types.d.ts b/src/services/types.d.ts index d38a1737..16219e46 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -750,6 +750,7 @@ interface IVergeConfig { enable_external_controller?: boolean; proxy_auto_config?: boolean; pac_file_content?: string; + proxy_host?: string; enable_random_port?: boolean; verge_mixed_port?: number; verge_socks_port?: number;