123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- 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 {
- match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) {
- Ok(map) => Self(Self::guard(map)),
- Err(err) => {
- log::error!(target: "app", "{err}");
- Self::template()
- }
- }
- }
- pub fn template() -> Self {
- let mut map = Mapping::new();
- let mut tun = Mapping::new();
- tun.insert("stack".into(), "gVisor".into());
- tun.insert("device".into(), "Meta".into());
- tun.insert("auto-route".into(), true.into());
- tun.insert("strict-route".into(), true.into());
- tun.insert("auto-detect-interface".into(), true.into());
- tun.insert("dns-hijack".into(), vec!["any:53", "tcp://any:53"].into());
- tun.insert("mtu".into(), 9000.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(), "info".into());
- map.insert("allow-lan".into(), false.into());
- map.insert("mode".into(), "rule".into());
- map.insert("external-controller".into(), "127.0.0.1:9097".into());
- map.insert("secret".into(), "".into());
- map.insert("tun".into(), tun.into());
- Self(map)
- }
- fn guard(mut config: Mapping) -> Mapping {
- 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);
- 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());
- 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,
- }),
- }
- }
- pub fn guard_mixed_port(config: &Mapping) -> u16 {
- let mut port = config
- .get("mixed-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(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(),
- }
- }
- }
- #[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 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>,
- }
- #[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>>,
- }
|