Skip to content

Commit

Permalink
feat(clash)!: add default core secret and impl port checker before cl…
Browse files Browse the repository at this point in the history
…ash start (#533)

* feat(clash): add default core secret to secure the communication

* fix(renovate): disable github auto merge for rust crates

* chore: wip

* chore: wip
  • Loading branch information
greenhat616 authored Mar 2, 2024
1 parent 6ad5149 commit 29c1d9c
Show file tree
Hide file tree
Showing 20 changed files with 203 additions and 36 deletions.
1 change: 1 addition & 0 deletions backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ tracing-appender = { version = "0.2", features = ["parking_lot"] }
base64 = "0.22"
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"
Expand Down
6 changes: 3 additions & 3 deletions backend/tauri/src/cmds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ pub async fn patch_verge_config(payload: IVerge) -> CmdResult {
}

#[tauri::command]
pub async fn change_clash_core(clash_core: Option<ClashCore>) -> CmdResult {
pub async fn change_clash_core(clash_core: Option<nyanpasu::ClashCore>) -> CmdResult {
wrap_err!(CoreManager::global().change_core(clash_core).await)
}

Expand Down Expand Up @@ -272,7 +272,7 @@ pub async fn fetch_latest_core_versions() -> CmdResult<ManifestVersionLatest> {
}

#[tauri::command]
pub async fn get_core_version(core_type: ClashCore) -> CmdResult<String> {
pub async fn get_core_version(core_type: nyanpasu::ClashCore) -> CmdResult<String> {
match tokio::task::spawn_blocking(move || resolve::resolve_core_version(&core_type)).await {
Ok(Ok(version)) => Ok(version),
Ok(Err(err)) => Err(format!("{err}")),
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -32,7 +39,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")]
Expand Down Expand Up @@ -84,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::<u16>().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")
Expand Down
6 changes: 4 additions & 2 deletions backend/tauri/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
25 changes: 25 additions & 0 deletions backend/tauri/src/config/nyanpasu/clash_strategy.rs
Original file line number Diff line number Diff line change
@@ -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()
}
}
16 changes: 12 additions & 4 deletions backend/tauri/src/config/nyanpasu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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<i64>,
/// 日记轮转时间,单位:天
pub max_log_files: Option<usize>,
Expand All @@ -152,7 +156,10 @@ pub struct IVerge {
pub verge_mixed_port: Option<u16>,

/// Check update when app launch
pub disbale_auto_check_update: Option<bool>,
pub disable_auto_check_update: Option<bool>,

/// Clash 相关策略
pub clash_strategy: Option<ClashStrategy>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize)]
Expand Down Expand Up @@ -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()
}
}
Expand Down Expand Up @@ -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);
Expand All @@ -273,5 +280,6 @@ impl IVerge {

patch!(max_log_files);
patch!(window_size_state);
patch!(clash_strategy);
}
}
17 changes: 14 additions & 3 deletions backend/tauri/src/core/clash/core.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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) => {
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion backend/tauri/src/core/updater.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
2 changes: 1 addition & 1 deletion backend/tauri/src/core/win_service.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![cfg(target_os = "windows")]

use crate::{
config::{ClashCore, Config},
config::{nyanpasu::ClashCore, Config},
utils::dirs,
};
use anyhow::{bail, Context, Result};
Expand Down
2 changes: 1 addition & 1 deletion backend/tauri/src/enhance/chain.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
config::{ClashCore, PrfItem},
config::{nyanpasu::ClashCore, PrfItem},
utils::{dirs, help},
};
use serde_yaml::Mapping;
Expand Down
16 changes: 15 additions & 1 deletion backend/tauri/src/feat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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::<u16>()?;
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()
Expand Down
26 changes: 26 additions & 0 deletions backend/tauri/src/utils/help.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::config::nyanpasu::ExternalControllerPortStrategy;
use anyhow::{anyhow, bail, Context, Result};
use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize};
Expand All @@ -7,6 +8,7 @@ use tauri::{
api::shell::{open, Program},
Manager,
};

/// read data from yaml as struct T
pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
if !path.exists() {
Expand Down Expand Up @@ -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<u16> {
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) => {
Expand Down
2 changes: 1 addition & 1 deletion backend/tauri/src/utils/init/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use tracing_appender::{
use tracing_log::log_tracer;
use tracing_subscriber::{filter, fmt, layer::SubscriberExt, reload, EnvFilter};

pub type ReloadSignal = (Option<config::logging::LoggingLevel>, Option<usize>);
pub type ReloadSignal = (Option<config::nyanpasu::LoggingLevel>, Option<usize>);

struct Channel(Option<Sender<ReloadSignal>>);
impl Channel {
Expand Down
5 changes: 4 additions & 1 deletion backend/tauri/src/utils/resolve.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{
config::{ClashCore, Config, IVerge, WindowState},
config::{
nyanpasu::{ClashCore, WindowState},
Config, IVerge,
},
core::{
tasks::{jobs::ProfilesJobGuard, JobsManager},
tray::proxies,
Expand Down
3 changes: 2 additions & 1 deletion renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
},
{
"matchManagers": ["cargo"],
"rangeStrategy": "update-lockfile"
"rangeStrategy": "update-lockfile",
"platformAutomerge": false
},
{
"groupName": "Bundler packages",
Expand Down
Loading

0 comments on commit 29c1d9c

Please sign in to comment.