use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::IpAddr; use super::defaults::*; // ============= Log Level ============= /// Logging verbosity level. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum LogLevel { /// All messages including trace (trace + debug + info + warn + error). Debug, /// Detailed operational logs (debug + info + warn + error). Verbose, /// Standard operational logs (info + warn + error). #[default] Normal, /// Minimal output: only warnings and errors (warn + error). /// Startup messages (config, DC connectivity, proxy links) are always shown /// via info! before the filter is applied. Silent, } impl LogLevel { /// Convert to tracing EnvFilter directive string. pub fn to_filter_str(&self) -> &'static str { match self { LogLevel::Debug => "trace", LogLevel::Verbose => "debug", LogLevel::Normal => "info", LogLevel::Silent => "warn", } } /// Parse from a loose string (CLI argument). pub fn from_str_loose(s: &str) -> Self { match s.to_lowercase().as_str() { "debug" | "trace" => LogLevel::Debug, "verbose" => LogLevel::Verbose, "normal" | "info" => LogLevel::Normal, "silent" | "quiet" | "error" | "warn" => LogLevel::Silent, _ => LogLevel::Normal, } } } impl std::fmt::Display for LogLevel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { LogLevel::Debug => write!(f, "debug"), LogLevel::Verbose => write!(f, "verbose"), LogLevel::Normal => write!(f, "normal"), LogLevel::Silent => write!(f, "silent"), } } } // ============= Sub-Configs ============= #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProxyModes { #[serde(default)] pub classic: bool, #[serde(default)] pub secure: bool, #[serde(default = "default_true")] pub tls: bool, } impl Default for ProxyModes { fn default() -> Self { Self { classic: true, secure: true, tls: true, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NetworkConfig { #[serde(default = "default_true")] pub ipv4: bool, /// None = auto-detect IPv6 availability. #[serde(default)] pub ipv6: Option, /// 4 or 6. #[serde(default = "default_prefer_4")] pub prefer: u8, #[serde(default)] pub multipath: bool, } impl Default for NetworkConfig { fn default() -> Self { Self { ipv4: true, ipv6: None, prefer: 4, multipath: false, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GeneralConfig { #[serde(default)] pub modes: ProxyModes, #[serde(default)] pub prefer_ipv6: bool, #[serde(default = "default_true")] pub fast_mode: bool, #[serde(default)] pub use_middle_proxy: bool, #[serde(default)] pub ad_tag: Option, /// Path to proxy-secret binary file (auto-downloaded if absent). /// Infrastructure secret from https://core.telegram.org/getProxySecret. #[serde(default)] pub proxy_secret_path: Option, /// Public IP override for middle-proxy NAT environments. /// When set, this IP is used in ME key derivation and RPC_PROXY_REQ "our_addr". #[serde(default)] pub middle_proxy_nat_ip: Option, /// Enable STUN-based NAT probing to discover public IP:port for ME KDF. #[serde(default)] pub middle_proxy_nat_probe: bool, /// Optional STUN server address (host:port) for NAT probing. #[serde(default)] pub middle_proxy_nat_stun: Option, /// Optional list of STUN servers for NAT probing fallback. #[serde(default)] pub middle_proxy_nat_stun_servers: Vec, /// Desired size of active Middle-Proxy writer pool. #[serde(default = "default_pool_size")] pub middle_proxy_pool_size: usize, /// Number of warm standby ME connections kept pre-initialized. #[serde(default)] pub middle_proxy_warm_standby: usize, /// Enable ME keepalive padding frames. #[serde(default = "default_true")] pub me_keepalive_enabled: bool, /// Keepalive interval in seconds. #[serde(default = "default_keepalive_interval")] pub me_keepalive_interval_secs: u64, /// Keepalive jitter in seconds. #[serde(default = "default_keepalive_jitter")] pub me_keepalive_jitter_secs: u64, /// Keepalive payload randomized (4 bytes); otherwise zeros. #[serde(default = "default_true")] pub me_keepalive_payload_random: bool, /// Enable staggered warmup of extra ME writers. #[serde(default = "default_true")] pub me_warmup_stagger_enabled: bool, /// Base delay between warmup connections in ms. #[serde(default = "default_warmup_step_delay_ms")] pub me_warmup_step_delay_ms: u64, /// Jitter for warmup delay in ms. #[serde(default = "default_warmup_step_jitter_ms")] pub me_warmup_step_jitter_ms: u64, /// Max concurrent reconnect attempts per DC. #[serde(default)] pub me_reconnect_max_concurrent_per_dc: u32, /// Base backoff in ms for reconnect. #[serde(default = "default_reconnect_backoff_base_ms")] pub me_reconnect_backoff_base_ms: u64, /// Cap backoff in ms for reconnect. #[serde(default = "default_reconnect_backoff_cap_ms")] pub me_reconnect_backoff_cap_ms: u64, /// Fast retry attempts before backoff. #[serde(default)] pub me_reconnect_fast_retry_count: u32, /// Ignore STUN/interface IP mismatch (keep using Middle Proxy even if NAT detected). #[serde(default)] pub stun_iface_mismatch_ignore: bool, /// Log unknown (non-standard) DC requests to a file (default: unknown-dc.txt). Set to null to disable. #[serde(default = "default_unknown_dc_log_path")] pub unknown_dc_log_path: Option, #[serde(default)] pub log_level: LogLevel, /// Disable colored output in logs (useful for files/systemd). #[serde(default)] pub disable_colors: bool, /// [general.links] — proxy link generation overrides. #[serde(default)] pub links: LinksConfig, } impl Default for GeneralConfig { fn default() -> Self { Self { modes: ProxyModes::default(), prefer_ipv6: false, fast_mode: true, use_middle_proxy: false, ad_tag: None, proxy_secret_path: None, middle_proxy_nat_ip: None, middle_proxy_nat_probe: false, middle_proxy_nat_stun: None, middle_proxy_nat_stun_servers: Vec::new(), middle_proxy_pool_size: default_pool_size(), middle_proxy_warm_standby: 0, me_keepalive_enabled: true, me_keepalive_interval_secs: default_keepalive_interval(), me_keepalive_jitter_secs: default_keepalive_jitter(), me_keepalive_payload_random: true, me_warmup_stagger_enabled: true, me_warmup_step_delay_ms: default_warmup_step_delay_ms(), me_warmup_step_jitter_ms: default_warmup_step_jitter_ms(), me_reconnect_max_concurrent_per_dc: 1, me_reconnect_backoff_base_ms: default_reconnect_backoff_base_ms(), me_reconnect_backoff_cap_ms: default_reconnect_backoff_cap_ms(), me_reconnect_fast_retry_count: 1, stun_iface_mismatch_ignore: false, unknown_dc_log_path: default_unknown_dc_log_path(), log_level: LogLevel::Normal, disable_colors: false, links: LinksConfig::default(), } } } /// `[general.links]` — proxy link generation settings. #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct LinksConfig { /// List of usernames whose tg:// links to display at startup. /// `"*"` = all users, `["alice", "bob"]` = specific users. #[serde(default)] pub show: ShowLink, /// Public hostname/IP for tg:// link generation (overrides detected IP). #[serde(default)] pub public_host: Option, /// Public port for tg:// link generation (overrides server.port). #[serde(default)] pub public_port: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { #[serde(default = "default_port")] pub port: u16, #[serde(default)] pub listen_addr_ipv4: Option, #[serde(default)] pub listen_addr_ipv6: Option, #[serde(default)] pub listen_unix_sock: Option, /// Unix socket file permissions (octal, e.g. "0666" or "0777"). /// Applied via chmod after bind. Default: no change (inherits umask). #[serde(default)] pub listen_unix_sock_perm: Option, /// Enable TCP listening. Default: true when no unix socket, false when /// listen_unix_sock is set. Set explicitly to override auto-detection. #[serde(default)] pub listen_tcp: Option, /// Accept HAProxy PROXY protocol headers on incoming connections. /// When enabled, real client IPs are extracted from PROXY v1/v2 headers. #[serde(default)] pub proxy_protocol: bool, #[serde(default)] pub metrics_port: Option, #[serde(default = "default_metrics_whitelist")] pub metrics_whitelist: Vec, #[serde(default)] pub listeners: Vec, } impl Default for ServerConfig { fn default() -> Self { Self { port: default_port(), listen_addr_ipv4: Some(default_listen_addr()), listen_addr_ipv6: Some("::".to_string()), listen_unix_sock: None, listen_unix_sock_perm: None, listen_tcp: None, proxy_protocol: false, metrics_port: None, metrics_whitelist: default_metrics_whitelist(), listeners: Vec::new(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TimeoutsConfig { #[serde(default = "default_handshake_timeout")] pub client_handshake: u64, #[serde(default = "default_connect_timeout")] pub tg_connect: u64, #[serde(default = "default_keepalive")] pub client_keepalive: u64, #[serde(default = "default_ack_timeout")] pub client_ack: u64, /// Number of quick ME reconnect attempts for single-address DC. #[serde(default = "default_me_one_retry")] pub me_one_retry: u8, /// Timeout per quick attempt in milliseconds for single-address DC. #[serde(default = "default_me_one_timeout")] pub me_one_timeout_ms: u64, } impl Default for TimeoutsConfig { fn default() -> Self { Self { client_handshake: default_handshake_timeout(), tg_connect: default_connect_timeout(), client_keepalive: default_keepalive(), client_ack: default_ack_timeout(), me_one_retry: default_me_one_retry(), me_one_timeout_ms: default_me_one_timeout(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AntiCensorshipConfig { #[serde(default = "default_tls_domain")] pub tls_domain: String, /// Additional TLS domains for generating multiple proxy links. #[serde(default)] pub tls_domains: Vec, #[serde(default = "default_true")] pub mask: bool, #[serde(default)] pub mask_host: Option, #[serde(default = "default_mask_port")] pub mask_port: u16, #[serde(default)] pub mask_unix_sock: Option, #[serde(default = "default_fake_cert_len")] pub fake_cert_len: usize, /// Enable TLS certificate emulation using cached real certificates. #[serde(default)] pub tls_emulation: bool, /// Directory to store TLS front cache (on disk). #[serde(default = "default_tls_front_dir")] pub tls_front_dir: String, } impl Default for AntiCensorshipConfig { fn default() -> Self { Self { tls_domain: default_tls_domain(), tls_domains: Vec::new(), mask: true, mask_host: None, mask_port: default_mask_port(), mask_unix_sock: None, fake_cert_len: default_fake_cert_len(), tls_emulation: false, tls_front_dir: default_tls_front_dir(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccessConfig { #[serde(default)] pub users: HashMap, #[serde(default)] pub user_max_tcp_conns: HashMap, #[serde(default)] pub user_expirations: HashMap>, #[serde(default)] pub user_data_quota: HashMap, #[serde(default)] pub user_max_unique_ips: HashMap, #[serde(default = "default_replay_check_len")] pub replay_check_len: usize, #[serde(default = "default_replay_window_secs")] pub replay_window_secs: u64, #[serde(default)] pub ignore_time_skew: bool, } impl Default for AccessConfig { fn default() -> Self { let mut users = HashMap::new(); users.insert( "default".to_string(), "00000000000000000000000000000000".to_string(), ); Self { users, user_max_tcp_conns: HashMap::new(), user_expirations: HashMap::new(), user_data_quota: HashMap::new(), user_max_unique_ips: HashMap::new(), replay_check_len: default_replay_check_len(), replay_window_secs: default_replay_window_secs(), ignore_time_skew: false, } } } // ============= Aux Structures ============= #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "type", rename_all = "lowercase")] pub enum UpstreamType { Direct { #[serde(default)] interface: Option, #[serde(default)] bind_addresses: Option>, }, Socks4 { address: String, #[serde(default)] interface: Option, #[serde(default)] user_id: Option, }, Socks5 { address: String, #[serde(default)] interface: Option, #[serde(default)] username: Option, #[serde(default)] password: Option, }, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpstreamConfig { #[serde(flatten)] pub upstream_type: UpstreamType, #[serde(default = "default_weight")] pub weight: u16, #[serde(default = "default_true")] pub enabled: bool, #[serde(default)] pub scopes: String, #[serde(skip)] pub selected_scope: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ListenerConfig { pub ip: IpAddr, /// IP address or hostname to announce in proxy links. /// Takes precedence over `announce_ip` if both are set. #[serde(default)] pub announce: Option, /// Deprecated: Use `announce` instead. IP address to announce in proxy links. /// Migrated to `announce` automatically if `announce` is not set. #[serde(default)] pub announce_ip: Option, } // ============= ShowLink ============= /// Controls which users' proxy links are displayed at startup. /// /// In TOML, this can be: /// - `show_link = "*"` — show links for all users /// - `show_link = ["a", "b"]` — show links for specific users /// - omitted — show no links (default) #[derive(Debug, Clone)] pub enum ShowLink { /// Don't show any links (default when omitted). None, /// Show links for all configured users. All, /// Show links for specific users. Specific(Vec), } impl Default for ShowLink { fn default() -> Self { ShowLink::None } } impl ShowLink { /// Returns true if no links should be shown. pub fn is_empty(&self) -> bool { matches!(self, ShowLink::None) || matches!(self, ShowLink::Specific(v) if v.is_empty()) } /// Resolve the list of user names to display, given all configured users. pub fn resolve_users<'a>(&'a self, all_users: &'a HashMap) -> Vec<&'a String> { match self { ShowLink::None => vec![], ShowLink::All => { let mut names: Vec<&String> = all_users.keys().collect(); names.sort(); names } ShowLink::Specific(names) => names.iter().collect(), } } } impl Serialize for ShowLink { fn serialize(&self, serializer: S) -> std::result::Result { match self { ShowLink::None => Vec::::new().serialize(serializer), ShowLink::All => serializer.serialize_str("*"), ShowLink::Specific(v) => v.serialize(serializer), } } } impl<'de> Deserialize<'de> for ShowLink { fn deserialize>(deserializer: D) -> std::result::Result { use serde::de; struct ShowLinkVisitor; impl<'de> de::Visitor<'de> for ShowLinkVisitor { type Value = ShowLink; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str(r#""*" or an array of user names"#) } fn visit_str(self, v: &str) -> std::result::Result { if v == "*" { Ok(ShowLink::All) } else { Err(de::Error::invalid_value( de::Unexpected::Str(v), &r#""*""#, )) } } fn visit_seq>(self, mut seq: A) -> std::result::Result { let mut names = Vec::new(); while let Some(name) = seq.next_element::()? { names.push(name); } if names.is_empty() { Ok(ShowLink::None) } else { Ok(ShowLink::Specific(names)) } } } deserializer.deserialize_any(ShowLinkVisitor) } }