use std::{ hash::Hasher, net::{IpAddr, SocketAddr}, path::PathBuf, sync::{Arc, Mutex}, }; use anyhow::Context; use serde::{Deserialize, Serialize}; use tokio::io::AsyncReadExt as _; use crate::{ common::stun::StunInfoCollector, instance::dns_server::DEFAULT_ET_DNS_ZONE, proto::{ acl::Acl, common::{CompressionAlgoPb, PortForwardConfigPb, SocketType}, }, tunnel::generate_digest_from_str, }; pub type Flags = crate::proto::common::FlagsInConfig; pub fn gen_default_flags() -> Flags { Flags { default_protocol: "tcp".to_string(), dev_name: "".to_string(), enable_encryption: true, enable_ipv6: true, mtu: 1380, latency_first: false, enable_exit_node: false, proxy_forward_by_system: false, no_tun: false, use_smoltcp: false, relay_network_whitelist: "*".to_string(), disable_p2p: false, p2p_only: false, relay_all_peer_rpc: false, disable_udp_hole_punching: false, multi_thread: true, data_compress_algo: CompressionAlgoPb::None.into(), bind_device: true, enable_kcp_proxy: false, disable_kcp_input: false, disable_relay_kcp: false, enable_relay_foreign_network_kcp: false, accept_dns: false, private_mode: false, enable_quic_proxy: false, disable_quic_input: false, quic_listen_port: 0, foreign_relay_bps_limit: u64::MAX, multi_thread_count: 2, encryption_algorithm: "aes-gcm".to_string(), disable_sym_hole_punching: false, tld_dns_zone: DEFAULT_ET_DNS_ZONE.to_string(), } } pub enum EncryptionAlgorithm { AesGcm, Aes256Gcm, Xor, #[cfg(feature = "wireguard")] ChaCha20, #[cfg(feature = "openssl-crypto")] OpensslAesGcm, #[cfg(feature = "openssl-crypto")] OpensslChacha20, #[cfg(feature = "openssl-crypto")] OpensslAes256Gcm, } impl std::fmt::Display for EncryptionAlgorithm { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::AesGcm => write!(f, "aes-gcm"), Self::Aes256Gcm => write!(f, "aes-256-gcm"), Self::Xor => write!(f, "xor"), #[cfg(feature = "wireguard")] Self::ChaCha20 => write!(f, "chacha20"), #[cfg(feature = "openssl-crypto")] Self::OpensslAesGcm => write!(f, "openssl-aes-gcm"), #[cfg(feature = "openssl-crypto")] Self::OpensslChacha20 => write!(f, "openssl-chacha20"), #[cfg(feature = "openssl-crypto")] Self::OpensslAes256Gcm => write!(f, "openssl-aes-256-gcm"), } } } impl TryFrom<&str> for EncryptionAlgorithm { type Error = anyhow::Error; fn try_from(value: &str) -> Result { match value { "aes-gcm" => Ok(Self::AesGcm), "aes-256-gcm" => Ok(Self::Aes256Gcm), "xor" => Ok(Self::Xor), #[cfg(feature = "wireguard")] "chacha20" => Ok(Self::ChaCha20), #[cfg(feature = "openssl-crypto")] "openssl-aes-gcm" => Ok(Self::OpensslAesGcm), #[cfg(feature = "openssl-crypto")] "openssl-chacha20" => Ok(Self::OpensslChacha20), #[cfg(feature = "openssl-crypto")] "openssl-aes-256-gcm" => Ok(Self::OpensslAes256Gcm), _ => Err(anyhow::anyhow!("invalid encryption algorithm")), } } } pub fn get_avaliable_encrypt_methods() -> Vec<&'static str> { let mut r = vec!["aes-gcm", "aes-256-gcm", "xor"]; if cfg!(feature = "wireguard") { r.push("chacha20"); } if cfg!(feature = "openssl-crypto") { r.extend(vec![ "openssl-aes-gcm", "openssl-chacha20", "openssl-aes-256-gcm", ]); } r } #[auto_impl::auto_impl(Box, &)] pub trait ConfigLoader: Send + Sync { fn get_id(&self) -> uuid::Uuid; fn set_id(&self, id: uuid::Uuid); fn get_hostname(&self) -> String; fn set_hostname(&self, name: Option); fn get_inst_name(&self) -> String; fn set_inst_name(&self, name: String); fn get_netns(&self) -> Option; fn set_netns(&self, ns: Option); fn get_ipv4(&self) -> Option; fn set_ipv4(&self, addr: Option); fn get_ipv6(&self) -> Option; fn set_ipv6(&self, addr: Option); fn get_dhcp(&self) -> bool; fn set_dhcp(&self, dhcp: bool); fn add_proxy_cidr( &self, cidr: cidr::Ipv4Cidr, mapped_cidr: Option, ) -> Result<(), anyhow::Error>; fn remove_proxy_cidr(&self, cidr: cidr::Ipv4Cidr); fn clear_proxy_cidrs(&self); fn get_proxy_cidrs(&self) -> Vec; fn get_network_identity(&self) -> NetworkIdentity; fn set_network_identity(&self, identity: NetworkIdentity); fn get_listener_uris(&self) -> Vec; fn get_peers(&self) -> Vec; fn set_peers(&self, peers: Vec); fn get_listeners(&self) -> Option>; fn set_listeners(&self, listeners: Vec); fn get_mapped_listeners(&self) -> Vec; fn set_mapped_listeners(&self, listeners: Option>); fn get_vpn_portal_config(&self) -> Option; fn set_vpn_portal_config(&self, config: VpnPortalConfig); fn get_flags(&self) -> Flags; fn set_flags(&self, flags: Flags); fn get_exit_nodes(&self) -> Vec; fn set_exit_nodes(&self, nodes: Vec); fn get_routes(&self) -> Option>; fn set_routes(&self, routes: Option>); fn get_socks5_portal(&self) -> Option; fn set_socks5_portal(&self, addr: Option); fn get_port_forwards(&self) -> Vec; fn set_port_forwards(&self, forwards: Vec); fn get_acl(&self) -> Option; fn set_acl(&self, acl: Option); fn get_tcp_whitelist(&self) -> Vec; fn set_tcp_whitelist(&self, whitelist: Vec); fn get_udp_whitelist(&self) -> Vec; fn set_udp_whitelist(&self, whitelist: Vec); fn get_stun_servers(&self) -> Option>; fn set_stun_servers(&self, servers: Option>); fn get_stun_servers_v6(&self) -> Option>; fn set_stun_servers_v6(&self, servers: Option>); fn dump(&self) -> String; } pub trait LoggingConfigLoader { fn get_file_logger_config(&self) -> FileLoggerConfig; fn get_console_logger_config(&self) -> ConsoleLoggerConfig; } pub type NetworkSecretDigest = [u8; 32]; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct NetworkIdentity { pub network_name: String, pub network_secret: Option, #[serde(skip)] pub network_secret_digest: Option, } #[derive(Eq, PartialEq, Hash)] struct NetworkIdentityWithOnlyDigest { network_name: String, network_secret_digest: Option, } impl From for NetworkIdentityWithOnlyDigest { fn from(identity: NetworkIdentity) -> Self { if identity.network_secret_digest.is_some() { Self { network_name: identity.network_name, network_secret_digest: identity.network_secret_digest, } } else if identity.network_secret.is_some() { let mut network_secret_digest = [0u8; 32]; generate_digest_from_str( &identity.network_name, identity.network_secret.as_ref().unwrap(), &mut network_secret_digest, ); Self { network_name: identity.network_name, network_secret_digest: Some(network_secret_digest), } } else { Self { network_name: identity.network_name, network_secret_digest: None, } } } } impl PartialEq for NetworkIdentity { fn eq(&self, other: &Self) -> bool { let self_with_digest = NetworkIdentityWithOnlyDigest::from(self.clone()); let other_with_digest = NetworkIdentityWithOnlyDigest::from(other.clone()); self_with_digest == other_with_digest } } impl Eq for NetworkIdentity {} impl std::hash::Hash for NetworkIdentity { fn hash(&self, state: &mut H) { let self_with_digest = NetworkIdentityWithOnlyDigest::from(self.clone()); self_with_digest.hash(state); } } impl NetworkIdentity { pub fn new(network_name: String, network_secret: String) -> Self { let mut network_secret_digest = [0u8; 32]; generate_digest_from_str(&network_name, &network_secret, &mut network_secret_digest); NetworkIdentity { network_name, network_secret: Some(network_secret), network_secret_digest: Some(network_secret_digest), } } } impl Default for NetworkIdentity { fn default() -> Self { Self::new("default".to_string(), "".to_string()) } } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct PeerConfig { pub uri: url::Url, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct ProxyNetworkConfig { pub cidr: cidr::Ipv4Cidr, // the CIDR of the proxy network pub mapped_cidr: Option, // allow remap the proxy CIDR to another CIDR pub allow: Option>, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] pub struct FileLoggerConfig { pub level: Option, pub file: Option, pub dir: Option, pub size_mb: Option, pub count: Option, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] pub struct ConsoleLoggerConfig { pub level: Option, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, derive_builder::Builder)] pub struct LoggingConfig { #[builder(setter(into, strip_option), default = None)] pub file_logger: Option, #[builder(setter(into, strip_option), default = None)] pub console_logger: Option, } impl LoggingConfigLoader for &LoggingConfig { fn get_file_logger_config(&self) -> FileLoggerConfig { self.file_logger.clone().unwrap_or_default() } fn get_console_logger_config(&self) -> ConsoleLoggerConfig { self.console_logger.clone().unwrap_or_default() } } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct VpnPortalConfig { pub client_cidr: cidr::Ipv4Cidr, pub wireguard_listen: SocketAddr, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] pub struct PortForwardConfig { pub bind_addr: SocketAddr, pub dst_addr: SocketAddr, pub proto: String, } impl From for PortForwardConfig { fn from(config: PortForwardConfigPb) -> Self { PortForwardConfig { bind_addr: config.bind_addr.unwrap_or_default().into(), dst_addr: config.dst_addr.unwrap_or_default().into(), proto: match SocketType::try_from(config.socket_type) { Ok(SocketType::Tcp) => "tcp".to_string(), Ok(SocketType::Udp) => "udp".to_string(), _ => "tcp".to_string(), }, } } } impl From for PortForwardConfigPb { fn from(val: PortForwardConfig) -> Self { PortForwardConfigPb { bind_addr: Some(val.bind_addr.into()), dst_addr: Some(val.dst_addr.into()), socket_type: match val.proto.to_lowercase().as_str() { "tcp" => SocketType::Tcp as i32, "udp" => SocketType::Udp as i32, _ => SocketType::Tcp as i32, }, } } } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] struct Config { netns: Option, hostname: Option, instance_name: Option, instance_id: Option, ipv4: Option, ipv6: Option, dhcp: Option, network_identity: Option, listeners: Option>, mapped_listeners: Option>, exit_nodes: Option>, peer: Option>, proxy_network: Option>, vpn_portal_config: Option, routes: Option>, socks5_proxy: Option, port_forward: Option>, flags: Option>, #[serde(skip)] flags_struct: Option, acl: Option, tcp_whitelist: Option>, udp_whitelist: Option>, stun_servers: Option>, stun_servers_v6: Option>, } #[derive(Debug, Clone)] pub struct TomlConfigLoader { config: Arc>, } impl Default for TomlConfigLoader { fn default() -> Self { TomlConfigLoader::new_from_str("").unwrap() } } impl TomlConfigLoader { pub fn new_from_str(config_str: &str) -> Result { let mut config = toml::de::from_str::(config_str) .with_context(|| format!("failed to parse config file: {}", config_str))?; config.flags_struct = Some(Self::gen_flags(config.flags.clone().unwrap_or_default())); let config = TomlConfigLoader { config: Arc::new(Mutex::new(config)), }; let old_ns = config.get_network_identity(); config.set_network_identity(NetworkIdentity::new( old_ns.network_name, old_ns.network_secret.unwrap_or_default(), )); Ok(config) } pub fn new(config_path: &PathBuf) -> Result { let config_str = std::fs::read_to_string(config_path) .with_context(|| format!("failed to read config file: {:?}", config_path))?; let ret = Self::new_from_str(&config_str)?; Ok(ret) } fn gen_flags(mut flags_hashmap: serde_json::Map) -> Flags { let default_flags_json = serde_json::to_string(&gen_default_flags()).unwrap(); let default_flags_hashmap = serde_json::from_str::>(&default_flags_json) .unwrap(); let mut merged_hashmap = serde_json::Map::new(); for (key, value) in default_flags_hashmap { if let Some(v) = flags_hashmap.remove(&key) { merged_hashmap.insert(key, v); } else { merged_hashmap.insert(key, value); } } serde_json::from_value(serde_json::Value::Object(merged_hashmap)).unwrap() } } impl ConfigLoader for TomlConfigLoader { fn get_inst_name(&self) -> String { self.config .lock() .unwrap() .instance_name .clone() .unwrap_or("default".to_string()) } fn set_inst_name(&self, name: String) { self.config.lock().unwrap().instance_name = Some(name); } fn get_hostname(&self) -> String { let hostname = self.config.lock().unwrap().hostname.clone(); match hostname { Some(hostname) => { let hostname = hostname .chars() .filter(|c| !c.is_control()) .take(32) .collect::(); if !hostname.is_empty() { self.set_hostname(Some(hostname.clone())); hostname } else { self.set_hostname(None); gethostname::gethostname().to_string_lossy().to_string() } } None => gethostname::gethostname().to_string_lossy().to_string(), } } fn set_hostname(&self, name: Option) { self.config.lock().unwrap().hostname = name; } fn get_netns(&self) -> Option { self.config.lock().unwrap().netns.clone() } fn set_netns(&self, ns: Option) { self.config.lock().unwrap().netns = ns; } fn get_ipv4(&self) -> Option { let locked_config = self.config.lock().unwrap(); locked_config .ipv4 .as_ref() .and_then(|s| s.parse().ok()) .map(|c: cidr::Ipv4Inet| { if c.network_length() == 32 { cidr::Ipv4Inet::new(c.address(), 24).unwrap() } else { c } }) } fn set_ipv4(&self, addr: Option) { self.config.lock().unwrap().ipv4 = addr.map(|addr| addr.to_string()); } fn get_ipv6(&self) -> Option { let locked_config = self.config.lock().unwrap(); locked_config.ipv6.as_ref().and_then(|s| s.parse().ok()) } fn set_ipv6(&self, addr: Option) { self.config.lock().unwrap().ipv6 = addr.map(|addr| addr.to_string()); } fn get_dhcp(&self) -> bool { self.config.lock().unwrap().dhcp.unwrap_or_default() } fn set_dhcp(&self, dhcp: bool) { self.config.lock().unwrap().dhcp = Some(dhcp); } fn add_proxy_cidr( &self, cidr: cidr::Ipv4Cidr, mapped_cidr: Option, ) -> Result<(), anyhow::Error> { let mut locked_config = self.config.lock().unwrap(); if locked_config.proxy_network.is_none() { locked_config.proxy_network = Some(vec![]); } if let Some(mapped_cidr) = mapped_cidr.as_ref() { if cidr.network_length() != mapped_cidr.network_length() { return Err(anyhow::anyhow!( "Mapped CIDR must have the same network length as the original CIDR: {} != {}", cidr.network_length(), mapped_cidr.network_length() )); } } // insert if no duplicate if !locked_config .proxy_network .as_ref() .unwrap() .iter() .any(|c| c.cidr == cidr && c.mapped_cidr == mapped_cidr) { locked_config .proxy_network .as_mut() .unwrap() .push(ProxyNetworkConfig { cidr, mapped_cidr, allow: None, }); } Ok(()) } fn remove_proxy_cidr(&self, cidr: cidr::Ipv4Cidr) { let mut locked_config = self.config.lock().unwrap(); if let Some(proxy_cidrs) = &mut locked_config.proxy_network { proxy_cidrs.retain(|c| c.cidr != cidr); } } fn clear_proxy_cidrs(&self) { let mut locked_config = self.config.lock().unwrap(); locked_config.proxy_network = None; } fn get_proxy_cidrs(&self) -> Vec { self.config .lock() .unwrap() .proxy_network .as_ref() .cloned() .unwrap_or_default() } fn get_id(&self) -> uuid::Uuid { let mut locked_config = self.config.lock().unwrap(); if locked_config.instance_id.is_none() { let id = uuid::Uuid::new_v4(); locked_config.instance_id = Some(id); id } else { *locked_config.instance_id.as_ref().unwrap() } } fn set_id(&self, id: uuid::Uuid) { self.config.lock().unwrap().instance_id = Some(id); } fn get_network_identity(&self) -> NetworkIdentity { self.config .lock() .unwrap() .network_identity .clone() .unwrap_or_default() } fn set_network_identity(&self, identity: NetworkIdentity) { self.config.lock().unwrap().network_identity = Some(identity); } fn get_listener_uris(&self) -> Vec { self.config .lock() .unwrap() .listeners .clone() .unwrap_or_default() } fn get_peers(&self) -> Vec { self.config.lock().unwrap().peer.clone().unwrap_or_default() } fn set_peers(&self, peers: Vec) { self.config.lock().unwrap().peer = Some(peers); } fn get_listeners(&self) -> Option> { self.config.lock().unwrap().listeners.clone() } fn set_listeners(&self, listeners: Vec) { self.config.lock().unwrap().listeners = Some(listeners); } fn get_mapped_listeners(&self) -> Vec { self.config .lock() .unwrap() .mapped_listeners .clone() .unwrap_or_default() } fn set_mapped_listeners(&self, listeners: Option>) { self.config.lock().unwrap().mapped_listeners = listeners; } fn get_vpn_portal_config(&self) -> Option { self.config.lock().unwrap().vpn_portal_config.clone() } fn set_vpn_portal_config(&self, config: VpnPortalConfig) { self.config.lock().unwrap().vpn_portal_config = Some(config); } fn get_flags(&self) -> Flags { self.config .lock() .unwrap() .flags_struct .clone() .unwrap_or_default() } fn set_flags(&self, flags: Flags) { self.config.lock().unwrap().flags_struct = Some(flags); } fn get_exit_nodes(&self) -> Vec { self.config .lock() .unwrap() .exit_nodes .clone() .unwrap_or_default() } fn set_exit_nodes(&self, nodes: Vec) { self.config.lock().unwrap().exit_nodes = Some(nodes); } fn get_routes(&self) -> Option> { self.config.lock().unwrap().routes.clone() } fn set_routes(&self, routes: Option>) { self.config.lock().unwrap().routes = routes; } fn get_socks5_portal(&self) -> Option { self.config.lock().unwrap().socks5_proxy.clone() } fn set_socks5_portal(&self, addr: Option) { self.config.lock().unwrap().socks5_proxy = addr; } fn get_port_forwards(&self) -> Vec { self.config .lock() .unwrap() .port_forward .clone() .unwrap_or_default() } fn set_port_forwards(&self, forwards: Vec) { self.config.lock().unwrap().port_forward = Some(forwards); } fn get_acl(&self) -> Option { self.config.lock().unwrap().acl.clone() } fn set_acl(&self, acl: Option) { self.config.lock().unwrap().acl = acl; } fn get_tcp_whitelist(&self) -> Vec { self.config .lock() .unwrap() .tcp_whitelist .clone() .unwrap_or_default() } fn set_tcp_whitelist(&self, whitelist: Vec) { self.config.lock().unwrap().tcp_whitelist = Some(whitelist); } fn get_udp_whitelist(&self) -> Vec { self.config .lock() .unwrap() .udp_whitelist .clone() .unwrap_or_default() } fn set_udp_whitelist(&self, whitelist: Vec) { self.config.lock().unwrap().udp_whitelist = Some(whitelist); } fn get_stun_servers(&self) -> Option> { self.config.lock().unwrap().stun_servers.clone() } fn set_stun_servers(&self, servers: Option>) { self.config.lock().unwrap().stun_servers = servers; } fn get_stun_servers_v6(&self) -> Option> { self.config.lock().unwrap().stun_servers_v6.clone() } fn set_stun_servers_v6(&self, servers: Option>) { self.config.lock().unwrap().stun_servers_v6 = servers; } fn dump(&self) -> String { let default_flags_json = serde_json::to_string(&gen_default_flags()).unwrap(); let default_flags_hashmap = serde_json::from_str::>(&default_flags_json) .unwrap(); let cur_flags_json = serde_json::to_string(&self.get_flags()).unwrap(); let cur_flags_hashmap = serde_json::from_str::>(&cur_flags_json) .unwrap(); let mut flag_map: serde_json::Map = Default::default(); for (key, value) in default_flags_hashmap { if let Some(v) = cur_flags_hashmap.get(&key) { if *v != value { flag_map.insert(key, v.clone()); } } } let mut config = self.config.lock().unwrap().clone(); config.flags = Some(flag_map); if config.stun_servers == Some(StunInfoCollector::get_default_servers()) { config.stun_servers = None; } if config.stun_servers_v6 == Some(StunInfoCollector::get_default_servers_v6()) { config.stun_servers_v6 = None; } toml::to_string_pretty(&config).unwrap() } } #[derive(Clone, Copy, Default)] pub struct ConfigFilePermission(u8); impl ConfigFilePermission { pub const READ_ONLY: u8 = 1 << 0; pub const NO_DELETE: u8 = 1 << 1; pub fn with_flag(self, flag: u8) -> Self { Self(self.0 | flag) } pub fn remove_flag(self, flag: u8) -> Self { Self(self.0 & !flag) } pub fn has_flag(&self, flag: u8) -> bool { (self.0 & flag) != 0 } } impl From for ConfigFilePermission { fn from(value: u8) -> Self { ConfigFilePermission(value) } } impl From for ConfigFilePermission { fn from(value: u32) -> Self { ConfigFilePermission(value as u8) } } impl From for u8 { fn from(value: ConfigFilePermission) -> Self { value.0 } } impl From for u32 { fn from(value: ConfigFilePermission) -> Self { value.0 as u32 } } impl std::fmt::Debug for ConfigFilePermission { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut flags = vec![]; if self.has_flag(ConfigFilePermission::READ_ONLY) { flags.push("READ_ONLY"); } else { flags.push("EDITABLE"); } if self.has_flag(ConfigFilePermission::NO_DELETE) { flags.push("NO_DELETE"); } else { flags.push("DELETABLE"); } write!(f, "{}", flags.join("|")) } } #[derive(Debug, Clone)] pub struct ConfigFileControl { pub path: Option, pub permission: ConfigFilePermission, } impl ConfigFileControl { pub const STATIC_CONFIG: ConfigFileControl = Self { path: None, permission: ConfigFilePermission( ConfigFilePermission::READ_ONLY | ConfigFilePermission::NO_DELETE, ), }; pub fn new(path: Option, permission: ConfigFilePermission) -> Self { ConfigFileControl { path, permission } } pub async fn from_path(path: PathBuf) -> Self { let read_only = if let Ok(metadata) = tokio::fs::metadata(&path).await { metadata.permissions().readonly() } else { true }; Self::new( Some(path), if read_only { ConfigFilePermission(ConfigFilePermission::READ_ONLY) } else { ConfigFilePermission(0) }, ) } pub fn is_read_only(&self) -> bool { self.permission.has_flag(ConfigFilePermission::READ_ONLY) } pub fn set_read_only(&mut self, read_only: bool) { if read_only { self.permission = self.permission.with_flag(ConfigFilePermission::READ_ONLY); } else { self.permission = self.permission.remove_flag(ConfigFilePermission::READ_ONLY); } } pub fn is_no_delete(&self) -> bool { self.permission.has_flag(ConfigFilePermission::NO_DELETE) } pub fn set_no_delete(&mut self, no_delete: bool) { if no_delete { self.permission = self.permission.with_flag(ConfigFilePermission::NO_DELETE); } else { self.permission = self.permission.remove_flag(ConfigFilePermission::NO_DELETE); } } pub fn is_deletable(&self) -> bool { !self.is_no_delete() } } pub async fn load_config_from_file( config_file: &PathBuf, config_dir: Option<&PathBuf>, ) -> Result<(TomlConfigLoader, ConfigFileControl), anyhow::Error> { if config_file.as_os_str() == "-" { let mut stdin = String::new(); _ = tokio::io::stdin() .read_to_string(&mut stdin) .await .context("failed to read config from stdin")?; let config = TomlConfigLoader::new_from_str(&stdin)?; return Ok((config, ConfigFileControl::STATIC_CONFIG)); } let config = TomlConfigLoader::new(config_file) .with_context(|| format!("failed to load config file: {:?}", config_file))?; let mut control = ConfigFileControl::from_path(config_file.clone()).await; if control.is_read_only() { control.set_no_delete(true); } else if let Some(config_dir) = config_dir { if let Some(config_file_dir) = config_file.parent() { // if the config file is in the config dir and named as the instance id, it can be saved remotely if config_file_dir == config_dir && config_file.file_stem() == Some(config.get_id().to_string().as_ref()) && config_file.extension() == Some(std::ffi::OsStr::new("toml")) { control.set_no_delete(false); } else { control.set_no_delete(true); } } } else { control.set_no_delete(true); } Ok((config, control)) } #[cfg(test)] pub mod tests { use super::*; #[test] fn test_stun_servers_config() { let config = TomlConfigLoader::default(); let stun_servers = config.get_stun_servers(); assert!(stun_servers.is_none()); // Test setting custom stun servers let custom_servers = vec!["txt:stun.easytier.cn".to_string()]; config.set_stun_servers(Some(custom_servers.clone())); let retrieved_servers = config.get_stun_servers(); assert_eq!(retrieved_servers.unwrap(), custom_servers); } #[test] fn test_stun_servers_toml_parsing() { let config_str = r#" instance_name = "test" stun_servers = [ "stun.l.google.com:19302", "stun1.l.google.com:19302", "txt:stun.easytier.cn" ]"#; let config = TomlConfigLoader::new_from_str(config_str).unwrap(); let stun_servers = config.get_stun_servers().unwrap(); assert_eq!(stun_servers.len(), 3); assert_eq!(stun_servers[0], "stun.l.google.com:19302"); assert_eq!(stun_servers[1], "stun1.l.google.com:19302"); assert_eq!(stun_servers[2], "txt:stun.easytier.cn"); } #[tokio::test] async fn full_example_test() { let config_str = r#" instance_name = "default" instance_id = "87ede5a2-9c3d-492d-9bbe-989b9d07e742" ipv4 = "10.144.144.10" listeners = [ "tcp://0.0.0.0:11010", "udp://0.0.0.0:11010" ] routes = [ "192.168.0.0/16" ] [network_identity] network_name = "default" network_secret = "" [[peer]] uri = "tcp://public.kkrainbow.top:11010" [[peer]] uri = "udp://192.168.94.33:11010" [[proxy_network]] cidr = "10.147.223.0/24" allow = ["tcp", "udp", "icmp"] [[proxy_network]] cidr = "10.1.1.0/24" allow = ["tcp", "icmp"] [file_logger] level = "info" file = "easytier" dir = "/tmp/easytier" [console_logger] level = "warn" [[port_forward]] bind_addr = "0.0.0.0:11011" dst_addr = "192.168.94.33:11011" proto = "tcp" "#; let ret = TomlConfigLoader::new_from_str(config_str); if let Err(e) = &ret { println!("{}", e); } else { println!("{:?}", ret.as_ref().unwrap()); } assert!(ret.is_ok()); let ret = ret.unwrap(); assert_eq!("10.144.144.10/24", ret.get_ipv4().unwrap().to_string()); assert_eq!( vec!["tcp://0.0.0.0:11010", "udp://0.0.0.0:11010"], ret.get_listener_uris() .iter() .map(|u| u.to_string()) .collect::>() ); assert_eq!( vec![PortForwardConfig { bind_addr: "0.0.0.0:11011".parse().unwrap(), dst_addr: "192.168.94.33:11011".parse().unwrap(), proto: "tcp".to_string(), }], ret.get_port_forwards() ); println!("{}", ret.dump()); } }