From 9418d77dd0d2db85b0bd434c6314f6440f9fa66c Mon Sep 17 00:00:00 2001 From: Jonson Petard Date: Fri, 1 Mar 2024 20:47:05 +0800 Subject: [PATCH 1/4] feat(clash): add default core secret to secure the communication --- backend/Cargo.lock | 1 + backend/tauri/Cargo.toml | 1 + backend/tauri/src/config/clash.rs | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 2882843ce0..60b154e1a9 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -723,6 +723,7 @@ dependencies = [ "tracing-futures", "tracing-log", "tracing-subscriber", + "uuid", "warp", "which 6.0.0", "window-shadows", diff --git a/backend/tauri/Cargo.toml b/backend/tauri/Cargo.toml index 2db1c32883..1340153a9a 100644 --- a/backend/tauri/Cargo.toml +++ b/backend/tauri/Cargo.toml @@ -83,6 +83,7 @@ tracing-appender = { version = "0.2", features = ["parking_lot"] } base64 = "0.21" single-instance = "0.3.3" tauri-plugin-deep-link = { path = "../tauri-plugin-deep-link", version = "0.1.2" } +uuid = "1.7.0" [target.'cfg(windows)'.dependencies] deelevate = "0.2.0" diff --git a/backend/tauri/src/config/clash.rs b/backend/tauri/src/config/clash.rs index abb1d8f502..574918fe50 100644 --- a/backend/tauri/src/config/clash.rs +++ b/backend/tauri/src/config/clash.rs @@ -32,7 +32,10 @@ impl IClashTemp { map.insert("external-controller".into(), "127.0.0.1:9872".into()); #[cfg(not(debug_assertions))] map.insert("external-controller".into(), "127.0.0.1:17650".into()); - map.insert("secret".into(), "".into()); + map.insert( + "secret".into(), + uuid::Uuid::new_v4().to_string().to_lowercase().into(), // generate a uuid v4 as default secret to secure the communication between clash and the client + ); #[cfg(feature = "default-meta")] map.insert("unified-delay".into(), true.into()); #[cfg(feature = "default-meta")] From 659ab0e0177b39171370edea20cb7bd87f475082 Mon Sep 17 00:00:00 2001 From: Jonson Petard Date: Sat, 2 Mar 2024 17:59:20 +0800 Subject: [PATCH 2/4] fix(renovate): disable github auto merge for rust crates --- renovate.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index ce56662334..b4607cd160 100644 --- a/renovate.json +++ b/renovate.json @@ -16,7 +16,8 @@ }, { "matchManagers": ["cargo"], - "rangeStrategy": "update-lockfile" + "rangeStrategy": "update-lockfile", + "platformAutomerge": false }, { "groupName": "Bundler packages", From 23d729afb3ccd0a2e359d3cebc40e2896638a639 Mon Sep 17 00:00:00 2001 From: Jonson Petard Date: Sat, 2 Mar 2024 20:36:59 +0800 Subject: [PATCH 3/4] chore: wip --- backend/tauri/src/cmds.rs | 6 +-- .../src/config/{clash.rs => clash/mod.rs} | 38 ++++++++++++++++++- backend/tauri/src/config/mod.rs | 6 ++- .../src/config/nyanpasu/clash_strategy.rs | 25 ++++++++++++ backend/tauri/src/config/nyanpasu/mod.rs | 16 ++++++-- backend/tauri/src/core/clash/core.rs | 17 +++++++-- backend/tauri/src/core/updater.rs | 2 +- backend/tauri/src/core/win_service.rs | 2 +- backend/tauri/src/enhance/chain.rs | 2 +- backend/tauri/src/feat.rs | 16 +++++++- backend/tauri/src/utils/help.rs | 26 +++++++++++++ backend/tauri/src/utils/init/logging.rs | 2 +- backend/tauri/src/utils/resolve.rs | 5 ++- src/components/layout/update-button.tsx | 14 +++---- src/components/setting/setting-verge.tsx | 8 ++-- src/services/types.d.ts | 2 +- 16 files changed, 156 insertions(+), 31 deletions(-) rename backend/tauri/src/config/{clash.rs => clash/mod.rs} (86%) create mode 100644 backend/tauri/src/config/nyanpasu/clash_strategy.rs diff --git a/backend/tauri/src/cmds.rs b/backend/tauri/src/cmds.rs index 8ea2471bcd..2e08df44e0 100644 --- a/backend/tauri/src/cmds.rs +++ b/backend/tauri/src/cmds.rs @@ -192,7 +192,7 @@ pub async fn patch_verge_config(payload: IVerge) -> CmdResult { } #[tauri::command] -pub async fn change_clash_core(clash_core: Option) -> CmdResult { +pub async fn change_clash_core(clash_core: Option) -> CmdResult { wrap_err!(CoreManager::global().change_core(clash_core).await) } @@ -272,7 +272,7 @@ pub async fn fetch_latest_core_versions() -> CmdResult { } #[tauri::command] -pub async fn get_core_version(core_type: ClashCore) -> CmdResult { +pub async fn get_core_version(core_type: nyanpasu::ClashCore) -> CmdResult { match tokio::task::spawn_blocking(move || resolve::resolve_core_version(&core_type)).await { Ok(Ok(version)) => Ok(version), Ok(Err(err)) => Err(format!("{err}")), @@ -305,7 +305,7 @@ pub async fn collect_logs() -> CmdResult { } #[tauri::command] -pub async fn update_core(core_type: ClashCore) -> CmdResult { +pub async fn update_core(core_type: nyanpasu::ClashCore) -> CmdResult { wrap_err!( updater::Updater::global() .read() diff --git a/backend/tauri/src/config/clash.rs b/backend/tauri/src/config/clash/mod.rs similarity index 86% rename from backend/tauri/src/config/clash.rs rename to backend/tauri/src/config/clash/mod.rs index 574918fe50..84f6546adb 100644 --- a/backend/tauri/src/config/clash.rs +++ b/backend/tauri/src/config/clash/mod.rs @@ -1,11 +1,18 @@ -use crate::utils::{dirs, help}; +use crate::utils::{ + dirs, + help::{self, get_clash_external_port}, +}; use anyhow::Result; +use log::warn; use serde::{Deserialize, Serialize}; use serde_yaml::{Mapping, Value}; use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, str::FromStr, }; +use tracing_attributes::instrument; + +use super::Config; #[derive(Default, Debug, Clone)] pub struct IClashTemp(pub Mapping); @@ -87,6 +94,35 @@ impl IClashTemp { } } + #[allow(dead_code)] + pub fn get_external_controller_port(&self) -> u16 { + let server = self.get_client_info().server; + let port = server.split(':').last().unwrap_or("9090"); + port.parse().unwrap_or(9090) + } + + #[instrument] + pub fn prepare_external_controller_port(&mut self) -> Result<()> { + let strategy = Config::verge() + .latest() + .get_external_controller_port_strategy(); + let server = self.get_client_info().server; + let (server_ip, server_port) = server.split_once(':').unwrap_or(("127.0.0.1", "9090")); + let server_port = server_port.parse::().unwrap_or(9090); + let port = get_clash_external_port(&strategy, server_port)?; + if port != server_port { + let new_server = format!("{}:{}", server_ip, port); + warn!( + "The external controller port has been changed to {}", + new_server + ); + let mut map = Mapping::new(); + map.insert("external-controller".into(), new_server.into()); + self.patch_config(map); + } + Ok(()) + } + pub fn guard_mixed_port(config: &Mapping) -> u16 { let mut port = config .get("mixed-port") diff --git a/backend/tauri/src/config/mod.rs b/backend/tauri/src/config/mod.rs index bf51e7e5ed..09aff15b1d 100644 --- a/backend/tauri/src/config/mod.rs +++ b/backend/tauri/src/config/mod.rs @@ -1,9 +1,11 @@ mod clash; mod core; mod draft; -mod nyanpasu; +pub mod nyanpasu; mod prfitem; mod profiles; mod runtime; -pub use self::{clash::*, core::*, draft::*, nyanpasu::*, prfitem::*, profiles::*, runtime::*}; +pub use self::{clash::*, core::*, draft::*, prfitem::*, profiles::*, runtime::*}; + +pub use self::nyanpasu::IVerge; diff --git a/backend/tauri/src/config/nyanpasu/clash_strategy.rs b/backend/tauri/src/config/nyanpasu/clash_strategy.rs new file mode 100644 index 0000000000..dd30788359 --- /dev/null +++ b/backend/tauri/src/config/nyanpasu/clash_strategy.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct ClashStrategy { + pub external_controller_port_strategy: ExternalControllerPortStrategy, +} + +#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ExternalControllerPortStrategy { + Fixed, + Random, + #[default] + AllowFallback, +} + +impl super::IVerge { + pub fn get_external_controller_port_strategy(&self) -> ExternalControllerPortStrategy { + self.clash_strategy + .as_ref() + .unwrap_or(&ClashStrategy::default()) + .external_controller_port_strategy + .to_owned() + } +} diff --git a/backend/tauri/src/config/nyanpasu/mod.rs b/backend/tauri/src/config/nyanpasu/mod.rs index c1f6aa9fc6..2608dfe824 100644 --- a/backend/tauri/src/config/nyanpasu/mod.rs +++ b/backend/tauri/src/config/nyanpasu/mod.rs @@ -3,8 +3,12 @@ use anyhow::Result; // use log::LevelFilter; use serde::{Deserialize, Serialize}; +mod clash_strategy; pub mod logging; +pub use self::clash_strategy::{ClashStrategy, ExternalControllerPortStrategy}; +pub use logging::LoggingLevel; + #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub enum ClashCore { #[serde(rename = "clash", alias = "clash-premium")] @@ -133,7 +137,7 @@ pub struct IVerge { /// 日志清理 /// 分钟数; 0 为不清理 - #[deprecated(note = "use `window_size_state` instead")] + #[deprecated(note = "use `max_log_files` instead")] pub auto_log_clean: Option, /// 日记轮转时间,单位:天 pub max_log_files: Option, @@ -152,7 +156,10 @@ pub struct IVerge { pub verge_mixed_port: Option, /// Check update when app launch - pub disbale_auto_check_update: Option, + pub disable_auto_check_update: Option, + + /// Clash 相关策略 + pub clash_strategy: Option, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -219,7 +226,7 @@ impl IVerge { page_transition_animation: Some("slide".into()), // auto_log_clean: Some(60 * 24 * 7), // 7 days 自动清理日记 max_log_files: Some(7), // 7 days - disbale_auto_check_update: Some(true), + disable_auto_check_update: Some(true), ..Self::default() } } @@ -247,7 +254,7 @@ impl IVerge { patch!(traffic_graph); patch!(enable_memory_usage); patch!(page_transition_animation); - patch!(disbale_auto_check_update); + patch!(disable_auto_check_update); patch!(enable_tun_mode); patch!(enable_service_mode); @@ -273,5 +280,6 @@ impl IVerge { patch!(max_log_files); patch!(window_size_state); + patch!(clash_strategy); } } diff --git a/backend/tauri/src/core/clash/core.rs b/backend/tauri/src/core/clash/core.rs index 1b41cb50ce..eebe647e0a 100644 --- a/backend/tauri/src/core/clash/core.rs +++ b/backend/tauri/src/core/clash/core.rs @@ -1,5 +1,10 @@ use super::api; -use crate::{config::*, core::logger::Logger, log_err, utils::dirs}; +use crate::{ + config::{nyanpasu::ClashCore, Config, ConfigType}, + core::logger::Logger, + log_err, + utils::dirs, +}; use anyhow::{bail, Context, Result}; use once_cell::sync::OnceCell; use parking_lot::Mutex; @@ -83,8 +88,6 @@ impl CoreManager { /// 启动核心 pub async fn run_core(&self) -> Result<()> { - let config_path = Config::generate_file(ConfigType::Run)?; - #[allow(unused_mut)] let mut should_kill = match self.sidecar.lock().take() { Some(child) => { @@ -106,6 +109,14 @@ impl CoreManager { if should_kill { sleep(Duration::from_millis(500)).await; } + + // 检查端口是否可用 + Config::clash() + .latest() + .prepare_external_controller_port()?; + + let config_path = Config::generate_file(ConfigType::Run)?; + #[cfg(target_os = "macos")] { let enable_tun = Config::verge().latest().enable_tun_mode; diff --git a/backend/tauri/src/core/updater.rs b/backend/tauri/src/core/updater.rs index d1da19446b..bd860b0197 100644 --- a/backend/tauri/src/core/updater.rs +++ b/backend/tauri/src/core/updater.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, io::Cursor, path::Path, sync::OnceLock}; use super::CoreManager; -use crate::config::ClashCore; +use crate::config::nyanpasu::ClashCore; use anyhow::{anyhow, Result}; use gunzip::Decompressor; use log::{debug, warn}; diff --git a/backend/tauri/src/core/win_service.rs b/backend/tauri/src/core/win_service.rs index 0b29adf6fb..4521cd1d56 100644 --- a/backend/tauri/src/core/win_service.rs +++ b/backend/tauri/src/core/win_service.rs @@ -1,7 +1,7 @@ #![cfg(target_os = "windows")] use crate::{ - config::{ClashCore, Config}, + config::{nyanpasu::ClashCore, Config}, utils::dirs, }; use anyhow::{bail, Context, Result}; diff --git a/backend/tauri/src/enhance/chain.rs b/backend/tauri/src/enhance/chain.rs index 99822cec52..8f03133399 100644 --- a/backend/tauri/src/enhance/chain.rs +++ b/backend/tauri/src/enhance/chain.rs @@ -1,5 +1,5 @@ use crate::{ - config::{ClashCore, PrfItem}, + config::{nyanpasu::ClashCore, PrfItem}, utils::{dirs, help}, }; use serde_yaml::Mapping; diff --git a/backend/tauri/src/feat.rs b/backend/tauri/src/feat.rs index dc2924725f..a3cd0006cf 100644 --- a/backend/tauri/src/feat.rs +++ b/backend/tauri/src/feat.rs @@ -8,7 +8,7 @@ use crate::{ config::*, core::*, log_err, - utils::{self, resolve}, + utils::{self, help::get_clash_external_port, resolve}, }; use anyhow::{bail, Result}; use serde_yaml::{Mapping, Value}; @@ -211,6 +211,20 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> { } }; + // 检测 external-controller port 是否修改 + if let Some(external_controller) = patch.get("external-controller") { + let external_controller = external_controller.as_str().unwrap(); + let changed = external_controller != Config::clash().data().get_client_info().server; + if changed { + let (_, port) = external_controller.split_once(':').unwrap(); + let port = port.parse::()?; + let strategy = Config::verge() + .latest() + .get_external_controller_port_strategy(); + get_clash_external_port(&strategy, port)?; // Do a check + } + } + // 激活配置 if mixed_port.is_some() || patch.get("secret").is_some() diff --git a/backend/tauri/src/utils/help.rs b/backend/tauri/src/utils/help.rs index 623ef16470..6e2d2e0d2c 100644 --- a/backend/tauri/src/utils/help.rs +++ b/backend/tauri/src/utils/help.rs @@ -1,3 +1,4 @@ +use crate::config::nyanpasu::ExternalControllerPortStrategy; use anyhow::{anyhow, bail, Context, Result}; use nanoid::nanoid; use serde::{de::DeserializeOwned, Serialize}; @@ -7,6 +8,7 @@ use tauri::{ api::shell::{open, Program}, Manager, }; + /// read data from yaml as struct T pub fn read_yaml(path: &PathBuf) -> Result { if !path.exists() { @@ -114,6 +116,30 @@ pub fn mapping_to_i18n_key(locale_key: &str) -> &'static str { } } +pub fn get_clash_external_port( + strategy: &ExternalControllerPortStrategy, + port: u16, +) -> anyhow::Result { + match strategy { + ExternalControllerPortStrategy::Fixed => { + if !port_scanner::local_port_available(port) { + bail!("Port {} is not available", port); + } + } + ExternalControllerPortStrategy::Random | ExternalControllerPortStrategy::AllowFallback => { + if ExternalControllerPortStrategy::AllowFallback == *strategy + && port_scanner::local_port_available(port) + { + return Ok(port); + } + let new_port = port_scanner::request_open_port() + .ok_or_else(|| anyhow!("Can't find an open port"))?; + return Ok(new_port); + } + } + Ok(port) +} + #[macro_export] macro_rules! error { ($result: expr) => { diff --git a/backend/tauri/src/utils/init/logging.rs b/backend/tauri/src/utils/init/logging.rs index cb5f7730e8..d6ca6e3456 100644 --- a/backend/tauri/src/utils/init/logging.rs +++ b/backend/tauri/src/utils/init/logging.rs @@ -17,7 +17,7 @@ use tracing_appender::{ }; use tracing_subscriber::{filter, fmt, layer::SubscriberExt, reload, EnvFilter}; -pub type ReloadSignal = (Option, Option); +pub type ReloadSignal = (Option, Option); struct Channel(Option>); impl Channel { diff --git a/backend/tauri/src/utils/resolve.rs b/backend/tauri/src/utils/resolve.rs index 5b6e0d33af..01c9cb6f14 100644 --- a/backend/tauri/src/utils/resolve.rs +++ b/backend/tauri/src/utils/resolve.rs @@ -1,5 +1,8 @@ use crate::{ - config::{ClashCore, Config, IVerge, WindowState}, + config::{ + nyanpasu::{ClashCore, WindowState}, + Config, IVerge, + }, core::{ tasks::{jobs::ProfilesJobGuard, JobsManager}, tray::proxies, diff --git a/src/components/layout/update-button.tsx b/src/components/layout/update-button.tsx index 03c44efe6b..8ca7cba25c 100644 --- a/src/components/layout/update-button.tsx +++ b/src/components/layout/update-button.tsx @@ -1,10 +1,10 @@ -import useSWR from "swr"; -import { useRef } from "react"; +import { useVerge } from "@/hooks/use-verge"; import { Button } from "@mui/material"; import { checkUpdate } from "@tauri-apps/api/updater"; -import { UpdateViewer } from "../setting/mods/update-viewer"; +import { useRef } from "react"; +import useSWR from "swr"; import { DialogRef } from "../base"; -import { useVerge } from "@/hooks/use-verge"; +import { UpdateViewer } from "../setting/mods/update-viewer"; interface Props { className?: string; @@ -17,11 +17,11 @@ export const UpdateButton = (props: Props) => { const { verge } = useVerge(); - const { disbale_auto_check_update } = verge ?? {}; + const { disable_auto_check_update } = verge ?? {}; const { data: updateInfo } = useSWR( - disbale_auto_check_update ? null : "checkUpdate", - disbale_auto_check_update ? null : checkUpdate, + disable_auto_check_update ? null : "checkUpdate", + disable_auto_check_update ? null : checkUpdate, { errorRetryCount: 2, revalidateIfStale: false, diff --git a/src/components/setting/setting-verge.tsx b/src/components/setting/setting-verge.tsx index fe260bdd87..9050da757c 100644 --- a/src/components/setting/setting-verge.tsx +++ b/src/components/setting/setting-verge.tsx @@ -23,6 +23,7 @@ import { checkUpdate } from "@tauri-apps/api/updater"; import { useLockFn } from "ahooks"; import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import MDYSwitch from "../common/mdy-switch"; import { ConfigViewer } from "./mods/config-viewer"; import { GuardState } from "./mods/guard-state"; import { HotkeyViewer } from "./mods/hotkey-viewer"; @@ -33,7 +34,6 @@ import { TasksViewer } from "./mods/tasks-viewer"; import { ThemeModeSwitch } from "./mods/theme-mode-switch"; import { ThemeViewer } from "./mods/theme-viewer"; import { UpdateViewer } from "./mods/update-viewer"; -import MDYSwitch from "../common/mdy-switch"; interface Props { onError?: (err: Error) => void; @@ -45,7 +45,7 @@ const SettingVerge = ({ onError }: Props) => { const { t } = useTranslation(); const { verge, patchVerge } = useVerge(); - const { theme_mode, language, disbale_auto_check_update } = verge ?? {}; + const { theme_mode, language, disable_auto_check_update } = verge ?? {}; const [loading, setLoading] = useState({ theme_mode: false, @@ -275,11 +275,11 @@ const SettingVerge = ({ onError }: Props) => { patchVerge({ disbale_auto_check_update: !e })} + onGuard={(e) => patchVerge({ disable_auto_check_update: !e })} > diff --git a/src/services/types.d.ts b/src/services/types.d.ts index e4b638139f..b3d17e1bd8 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -161,7 +161,7 @@ interface IVergeConfig { traffic_graph?: boolean; enable_memory_usage?: boolean; page_transition_animation?: keyof typeof import("@/components/layout/page-transition").pageTransitionVariants; - disbale_auto_check_update?: boolean; + disable_auto_check_update?: boolean; enable_tun_mode?: boolean; enable_auto_launch?: boolean; enable_service_mode?: boolean; From a3761f1802749594fad4af477bf6357947784179 Mon Sep 17 00:00:00 2001 From: Jonson Petard Date: Sat, 2 Mar 2024 21:10:43 +0800 Subject: [PATCH 4/4] chore: wip --- .../setting/mods/controller-viewer.tsx | 38 +++++++++++++++++-- src/services/types.d.ts | 4 ++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/components/setting/mods/controller-viewer.tsx b/src/components/setting/mods/controller-viewer.tsx index 326d2412ab..50db0f32fb 100644 --- a/src/components/setting/mods/controller-viewer.tsx +++ b/src/components/setting/mods/controller-viewer.tsx @@ -1,7 +1,15 @@ import { BaseDialog, DialogRef } from "@/components/base"; import { useClashInfo } from "@/hooks/use-clash"; import { NotificationType, useNotification } from "@/hooks/use-notification"; -import { List, ListItem, ListItemText, TextField } from "@mui/material"; +import { useVerge } from "@/hooks/use-verge"; +import { + List, + ListItem, + ListItemText, + MenuItem, + Select, + TextField, +} from "@mui/material"; import { useLockFn } from "ahooks"; import { forwardRef, useImperativeHandle, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -12,9 +20,13 @@ export const ControllerViewer = forwardRef((props, ref) => { const [loading, setLoading] = useState(false); const { clashInfo, patchInfo } = useClashInfo(); - + const { verge, patchVerge } = useVerge(); const [controller, setController] = useState(clashInfo?.server || ""); const [secret, setSecret] = useState(clashInfo?.secret || ""); + const [portStrategy, setPortStrategy] = useState( + verge?.clash_strategy?.external_controller_port_strategy || + "allow_fallback", + ); useImperativeHandle(ref, () => ({ open: () => { @@ -28,6 +40,9 @@ export const ControllerViewer = forwardRef((props, ref) => { const onSave = useLockFn(async () => { try { setLoading(true); + await patchVerge({ + clash_strategy: { external_controller_port_strategy: portStrategy }, + }); await patchInfo({ "external-controller": controller, secret }); useNotification({ title: t("Success"), @@ -49,7 +64,7 @@ export const ControllerViewer = forwardRef((props, ref) => { return ( ((props, ref) => { /> + + + + +