This commit implements a comprehensive migration from legacy service IPC to the kode-bridge library for Windows IPC communication. Key changes include: Replace service_ipc with kode-bridge IpcManager for all mihomo communications Simplify proxy commands using new caching mechanism with ProxyRequestCache Add Windows named pipe (\.\pipe\mihomo) and Unix socket IPC endpoint configuration Update Tauri permissions and dependencies (dashmap, tauri-plugin-notification) Add IPC logging support and improve error handling Fix Windows IPC path handling in directory utilities This migration enables better cross-platform IPC support and improved performance for mihomo proxy core communication.
434 lines
14 KiB
Rust
434 lines
14 KiB
Rust
use crate::utils::{dirs, help};
|
|
use anyhow::Result;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_yaml::{Mapping, Value};
|
|
use std::{
|
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
|
str::FromStr,
|
|
};
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
pub struct IClashTemp(pub Mapping);
|
|
|
|
impl IClashTemp {
|
|
pub fn new() -> Self {
|
|
let template = Self::template();
|
|
match dirs::clash_path().and_then(|path| help::read_mapping(&path)) {
|
|
Ok(mut map) => {
|
|
template.0.keys().for_each(|key| {
|
|
if !map.contains_key(key) {
|
|
map.insert(key.clone(), template.0.get(key).unwrap().clone());
|
|
}
|
|
});
|
|
// 确保 secret 字段存在且不为空
|
|
if let Some(Value::String(s)) = map.get_mut("secret") {
|
|
if s.is_empty() {
|
|
*s = "set-your-secret".to_string();
|
|
}
|
|
}
|
|
Self(Self::guard(map))
|
|
}
|
|
Err(err) => {
|
|
log::error!(target: "app", "{err}");
|
|
template
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn template() -> Self {
|
|
let mut map = Mapping::new();
|
|
let mut tun = Mapping::new();
|
|
let mut cors_map = Mapping::new();
|
|
tun.insert("enable".into(), false.into());
|
|
tun.insert("stack".into(), "gvisor".into());
|
|
tun.insert("auto-route".into(), true.into());
|
|
tun.insert("strict-route".into(), false.into());
|
|
tun.insert("auto-detect-interface".into(), true.into());
|
|
tun.insert("dns-hijack".into(), vec!["any:53"].into());
|
|
#[cfg(not(target_os = "windows"))]
|
|
map.insert("redir-port".into(), 7895.into());
|
|
#[cfg(target_os = "linux")]
|
|
map.insert("tproxy-port".into(), 7896.into());
|
|
map.insert("mixed-port".into(), 7897.into());
|
|
map.insert("socks-port".into(), 7898.into());
|
|
map.insert("port".into(), 7899.into());
|
|
map.insert("log-level".into(), "warning".into());
|
|
map.insert("allow-lan".into(), false.into());
|
|
map.insert("ipv6".into(), true.into());
|
|
map.insert("mode".into(), "rule".into());
|
|
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
|
#[cfg(unix)]
|
|
map.insert("external-controller-unix".into(), "mihomo.sock".into());
|
|
#[cfg(windows)]
|
|
map.insert("external-controller-pipe".into(), r"\\.\pipe\mihomo".into());
|
|
cors_map.insert("allow-private-network".into(), true.into());
|
|
cors_map.insert(
|
|
"allow-origins".into(),
|
|
vec![
|
|
"tauri://localhost",
|
|
"http://tauri.localhost",
|
|
// Only enable this in dev mode
|
|
#[cfg(feature = "verge-dev")]
|
|
"http://localhost:3000",
|
|
"https://yacd.metacubex.one",
|
|
"https://metacubex.github.io",
|
|
"https://board.zash.run.place",
|
|
]
|
|
.into(),
|
|
);
|
|
map.insert("secret".into(), "set-your-secret".into());
|
|
map.insert("tun".into(), tun.into());
|
|
map.insert("external-controller-cors".into(), cors_map.into());
|
|
map.insert("unified-delay".into(), true.into());
|
|
Self(map)
|
|
}
|
|
|
|
fn guard(mut config: Mapping) -> Mapping {
|
|
#[cfg(not(target_os = "windows"))]
|
|
let redir_port = Self::guard_redir_port(&config);
|
|
#[cfg(target_os = "linux")]
|
|
let tproxy_port = Self::guard_tproxy_port(&config);
|
|
let mixed_port = Self::guard_mixed_port(&config);
|
|
let socks_port = Self::guard_socks_port(&config);
|
|
let port = Self::guard_port(&config);
|
|
let ctrl = Self::guard_server_ctrl(&config);
|
|
#[cfg(unix)]
|
|
let external_controller_unix = Self::guard_external_controller_unix(&config);
|
|
#[cfg(windows)]
|
|
let external_controller_pipe = Self::guard_external_controller_pipe(&config);
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
config.insert("redir-port".into(), redir_port.into());
|
|
#[cfg(target_os = "linux")]
|
|
config.insert("tproxy-port".into(), tproxy_port.into());
|
|
config.insert("mixed-port".into(), mixed_port.into());
|
|
config.insert("socks-port".into(), socks_port.into());
|
|
config.insert("port".into(), port.into());
|
|
config.insert("external-controller".into(), ctrl.into());
|
|
#[cfg(unix)]
|
|
config.insert(
|
|
"external-controller-unix".into(),
|
|
external_controller_unix.into(),
|
|
);
|
|
#[cfg(windows)]
|
|
config.insert(
|
|
"external-controller-pipe".into(),
|
|
external_controller_pipe.into(),
|
|
);
|
|
config
|
|
}
|
|
|
|
pub fn patch_config(&mut self, patch: Mapping) {
|
|
for (key, value) in patch.into_iter() {
|
|
self.0.insert(key, value);
|
|
}
|
|
}
|
|
|
|
pub fn save_config(&self) -> Result<()> {
|
|
help::save_yaml(
|
|
&dirs::clash_path()?,
|
|
&self.0,
|
|
Some("# Generated by Clash Verge"),
|
|
)
|
|
}
|
|
|
|
pub fn get_mixed_port(&self) -> u16 {
|
|
Self::guard_mixed_port(&self.0)
|
|
}
|
|
|
|
#[allow(unused)]
|
|
pub fn get_socks_port(&self) -> u16 {
|
|
Self::guard_socks_port(&self.0)
|
|
}
|
|
|
|
#[allow(unused)]
|
|
pub fn get_port(&self) -> u16 {
|
|
Self::guard_port(&self.0)
|
|
}
|
|
|
|
pub fn get_client_info(&self) -> ClashInfo {
|
|
let config = &self.0;
|
|
|
|
ClashInfo {
|
|
mixed_port: Self::guard_mixed_port(config),
|
|
socks_port: Self::guard_socks_port(config),
|
|
port: Self::guard_port(config),
|
|
server: Self::guard_client_ctrl(config),
|
|
secret: config.get("secret").and_then(|value| match value {
|
|
Value::String(val_str) => Some(val_str.clone()),
|
|
Value::Bool(val_bool) => Some(val_bool.to_string()),
|
|
Value::Number(val_num) => Some(val_num.to_string()),
|
|
_ => None,
|
|
}),
|
|
}
|
|
}
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn guard_redir_port(config: &Mapping) -> u16 {
|
|
let mut port = config
|
|
.get("redir-port")
|
|
.and_then(|value| match value {
|
|
Value::String(val_str) => val_str.parse().ok(),
|
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
|
_ => None,
|
|
})
|
|
.unwrap_or(7895);
|
|
if port == 0 {
|
|
port = 7895;
|
|
}
|
|
port
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub fn guard_tproxy_port(config: &Mapping) -> u16 {
|
|
let mut port = config
|
|
.get("tproxy-port")
|
|
.and_then(|value| match value {
|
|
Value::String(val_str) => val_str.parse().ok(),
|
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
|
_ => None,
|
|
})
|
|
.unwrap_or(7896);
|
|
if port == 0 {
|
|
port = 7896;
|
|
}
|
|
port
|
|
}
|
|
|
|
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
|
let raw_value = config.get("mixed-port");
|
|
|
|
let mut port = raw_value
|
|
.and_then(|value| match value {
|
|
Value::String(val_str) => val_str.parse().ok(),
|
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
|
_ => None,
|
|
})
|
|
.unwrap_or(7897);
|
|
|
|
if port == 0 {
|
|
port = 7897;
|
|
}
|
|
|
|
port
|
|
}
|
|
|
|
pub fn guard_socks_port(config: &Mapping) -> u16 {
|
|
let mut port = config
|
|
.get("socks-port")
|
|
.and_then(|value| match value {
|
|
Value::String(val_str) => val_str.parse().ok(),
|
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
|
_ => None,
|
|
})
|
|
.unwrap_or(7898);
|
|
if port == 0 {
|
|
port = 7898;
|
|
}
|
|
port
|
|
}
|
|
|
|
pub fn guard_port(config: &Mapping) -> u16 {
|
|
let mut port = config
|
|
.get("port")
|
|
.and_then(|value| match value {
|
|
Value::String(val_str) => val_str.parse().ok(),
|
|
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
|
_ => None,
|
|
})
|
|
.unwrap_or(7899);
|
|
if port == 0 {
|
|
port = 7899;
|
|
}
|
|
port
|
|
}
|
|
|
|
pub fn guard_server_ctrl(config: &Mapping) -> String {
|
|
config
|
|
.get("external-controller")
|
|
.and_then(|value| match value.as_str() {
|
|
Some(val_str) => {
|
|
let val_str = val_str.trim();
|
|
|
|
let val = match val_str.starts_with(':') {
|
|
true => format!("127.0.0.1{val_str}"),
|
|
false => val_str.to_owned(),
|
|
};
|
|
|
|
SocketAddr::from_str(val.as_str())
|
|
.ok()
|
|
.map(|s| s.to_string())
|
|
}
|
|
None => None,
|
|
})
|
|
.unwrap_or("127.0.0.1:9097".into())
|
|
}
|
|
|
|
pub fn guard_client_ctrl(config: &Mapping) -> String {
|
|
let value = Self::guard_server_ctrl(config);
|
|
match SocketAddr::from_str(value.as_str()) {
|
|
Ok(mut socket) => {
|
|
if socket.ip().is_unspecified() {
|
|
socket.set_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
|
}
|
|
socket.to_string()
|
|
}
|
|
Err(_) => "127.0.0.1:9097".into(),
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
pub fn guard_external_controller_unix(config: &Mapping) -> String {
|
|
config
|
|
.get("external-controller-unix")
|
|
.and_then(|value| value.as_str())
|
|
.map(|s| s.trim().to_string())
|
|
.filter(|s| !s.is_empty())
|
|
.unwrap_or_else(|| "mihomo.sock".to_string())
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub fn guard_external_controller_pipe(config: &Mapping) -> String {
|
|
config
|
|
.get("external-controller-pipe")
|
|
.and_then(|value| value.as_str())
|
|
.map(|s| s.trim().to_string())
|
|
.filter(|s| !s.is_empty())
|
|
.unwrap_or_else(|| r"\\.\pipe\mihomo".to_string())
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
pub struct ClashInfo {
|
|
/// clash core port
|
|
pub mixed_port: u16,
|
|
pub socks_port: u16,
|
|
pub port: u16,
|
|
/// same as `external-controller`
|
|
pub server: String,
|
|
/// clash secret
|
|
pub secret: Option<String>,
|
|
}
|
|
|
|
#[test]
|
|
fn test_clash_info() {
|
|
fn get_case<T: Into<Value>, D: Into<Value>>(mp: T, ec: D) -> ClashInfo {
|
|
let mut map = Mapping::new();
|
|
map.insert("mixed-port".into(), mp.into());
|
|
map.insert("external-controller".into(), ec.into());
|
|
|
|
IClashTemp(IClashTemp::guard(map)).get_client_info()
|
|
}
|
|
|
|
fn get_result<S: Into<String>>(port: u16, server: S) -> ClashInfo {
|
|
ClashInfo {
|
|
mixed_port: port,
|
|
socks_port: 7898,
|
|
port: 7899,
|
|
server: server.into(),
|
|
secret: None,
|
|
}
|
|
}
|
|
|
|
assert_eq!(
|
|
IClashTemp(IClashTemp::guard(Mapping::new())).get_client_info(),
|
|
get_result(7897, "127.0.0.1:9097")
|
|
);
|
|
|
|
assert_eq!(get_case("", ""), get_result(7897, "127.0.0.1:9097"));
|
|
|
|
assert_eq!(get_case(65537, ""), get_result(1, "127.0.0.1:9097"));
|
|
|
|
assert_eq!(
|
|
get_case(8888, "127.0.0.1:8888"),
|
|
get_result(8888, "127.0.0.1:8888")
|
|
);
|
|
|
|
assert_eq!(
|
|
get_case(8888, " :98888 "),
|
|
get_result(8888, "127.0.0.1:9097")
|
|
);
|
|
|
|
assert_eq!(
|
|
get_case(8888, "0.0.0.0:8080 "),
|
|
get_result(8888, "127.0.0.1:8080")
|
|
);
|
|
|
|
assert_eq!(
|
|
get_case(8888, "0.0.0.0:8080"),
|
|
get_result(8888, "127.0.0.1:8080")
|
|
);
|
|
|
|
assert_eq!(
|
|
get_case(8888, "[::]:8080"),
|
|
get_result(8888, "127.0.0.1:8080")
|
|
);
|
|
|
|
assert_eq!(
|
|
get_case(8888, "192.168.1.1:8080"),
|
|
get_result(8888, "192.168.1.1:8080")
|
|
);
|
|
|
|
assert_eq!(
|
|
get_case(8888, "192.168.1.1:80800"),
|
|
get_result(8888, "127.0.0.1:9097")
|
|
);
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub struct IClashExternalControllerCors {
|
|
pub allow_origins: Option<Vec<String>>,
|
|
pub allow_private_network: Option<bool>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub struct IClash {
|
|
pub mixed_port: Option<u16>,
|
|
pub allow_lan: Option<bool>,
|
|
pub log_level: Option<String>,
|
|
pub ipv6: Option<bool>,
|
|
pub mode: Option<String>,
|
|
pub external_controller: Option<String>,
|
|
pub secret: Option<String>,
|
|
pub dns: Option<IClashDNS>,
|
|
pub tun: Option<IClashTUN>,
|
|
pub interface_name: Option<String>,
|
|
pub external_controller_cors: Option<IClashExternalControllerCors>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub struct IClashTUN {
|
|
pub enable: Option<bool>,
|
|
pub stack: Option<String>,
|
|
pub auto_route: Option<bool>,
|
|
pub auto_detect_interface: Option<bool>,
|
|
pub dns_hijack: Option<Vec<String>>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub struct IClashDNS {
|
|
pub enable: Option<bool>,
|
|
pub listen: Option<String>,
|
|
pub default_nameserver: Option<Vec<String>>,
|
|
pub enhanced_mode: Option<String>,
|
|
pub fake_ip_range: Option<String>,
|
|
pub use_hosts: Option<bool>,
|
|
pub fake_ip_filter: Option<Vec<String>>,
|
|
pub nameserver: Option<Vec<String>>,
|
|
pub fallback: Option<Vec<String>>,
|
|
pub fallback_filter: Option<IClashFallbackFilter>,
|
|
pub nameserver_policy: Option<Vec<String>>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub struct IClashFallbackFilter {
|
|
pub geoip: Option<bool>,
|
|
pub geoip_code: Option<String>,
|
|
pub ipcidr: Option<Vec<String>>,
|
|
pub domain: Option<Vec<String>>,
|
|
}
|