From 896e129155684a5547ab5c5a0de15c3cca803528 Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:48:22 +0300 Subject: [PATCH 1/5] Checked defaults --- src/config/defaults.rs | 33 ++++++++++++++++---- src/config/types.rs | 68 +++++++++++++++++++++++++----------------- 2 files changed, 69 insertions(+), 32 deletions(-) diff --git a/src/config/defaults.rs b/src/config/defaults.rs index 51abe65..a2b2fc7 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -7,7 +7,7 @@ const DEFAULT_NETWORK_IPV6: Option = Some(false); const DEFAULT_STUN_TCP_FALLBACK: bool = true; const DEFAULT_MIDDLE_PROXY_WARM_STANDBY: usize = 16; const DEFAULT_ME_RECONNECT_MAX_CONCURRENT_PER_DC: u32 = 8; -const DEFAULT_ME_RECONNECT_FAST_RETRY_COUNT: u32 = 12; +const DEFAULT_ME_RECONNECT_FAST_RETRY_COUNT: u32 = 11; const DEFAULT_LISTEN_ADDR_IPV6: &str = "::"; const DEFAULT_ACCESS_USER: &str = "default"; const DEFAULT_ACCESS_SECRET: &str = "00000000000000000000000000000000"; @@ -21,7 +21,7 @@ pub(crate) fn default_port() -> u16 { } pub(crate) fn default_tls_domain() -> String { - "www.google.com".to_string() + "petrovich.ru".to_string() } pub(crate) fn default_mask_port() -> u16 { @@ -45,7 +45,7 @@ pub(crate) fn default_replay_window_secs() -> u64 { } pub(crate) fn default_handshake_timeout() -> u64 { - 15 + 30 } pub(crate) fn default_connect_timeout() -> u64 { @@ -60,17 +60,21 @@ pub(crate) fn default_ack_timeout() -> u64 { 300 } pub(crate) fn default_me_one_retry() -> u8 { - 3 + 12 } pub(crate) fn default_me_one_timeout() -> u64 { - 1500 + 1200 } pub(crate) fn default_listen_addr() -> String { "0.0.0.0".to_string() } +pub(crate) fn default_listen_addr_ipv4() -> Option { + Some(default_listen_addr()) +} + pub(crate) fn default_weight() -> u16 { 1 } @@ -102,6 +106,21 @@ pub(crate) fn default_pool_size() -> usize { 8 } +pub(crate) fn default_proxy_secret_path() -> Option { + Some("proxy-secret".to_string()) +} + +pub(crate) fn default_middle_proxy_nat_stun() -> Option { + Some("stun.l.google.com:19302".to_string()) +} + +pub(crate) fn default_middle_proxy_nat_stun_servers() -> Vec { + vec![ + "stun1.l.google.com:19302".to_string(), + "stun2.l.google.com:19302".to_string(), + ] +} + pub(crate) fn default_middle_proxy_warm_standby() -> usize { DEFAULT_MIDDLE_PROXY_WARM_STANDBY } @@ -303,6 +322,10 @@ pub(crate) fn default_listen_addr_ipv6() -> String { DEFAULT_LISTEN_ADDR_IPV6.to_string() } +pub(crate) fn default_listen_addr_ipv6_opt() -> Option { + Some(default_listen_addr_ipv6()) +} + pub(crate) fn default_access_users() -> HashMap { HashMap::from([( DEFAULT_ACCESS_USER.to_string(), diff --git a/src/config/types.rs b/src/config/types.rs index 1302a97..f42a94a 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -87,7 +87,7 @@ pub struct NetworkConfig { pub ipv4: bool, /// None = auto-detect IPv6 availability. - #[serde(default)] + #[serde(default = "default_network_ipv6")] pub ipv6: Option, /// 4 or 6. @@ -102,7 +102,7 @@ pub struct NetworkConfig { pub stun_servers: Vec, /// Enable TCP STUN fallback when UDP is blocked. - #[serde(default)] + #[serde(default = "default_stun_tcp_fallback")] pub stun_tcp_fallback: bool, /// HTTP-based public IP detection endpoints (fallback after STUN). @@ -140,7 +140,7 @@ pub struct GeneralConfig { #[serde(default = "default_true")] pub fast_mode: bool, - #[serde(default)] + #[serde(default = "default_true")] pub use_middle_proxy: bool, #[serde(default)] @@ -148,7 +148,7 @@ pub struct GeneralConfig { /// Path to proxy-secret binary file (auto-downloaded if absent). /// Infrastructure secret from https://core.telegram.org/getProxySecret. - #[serde(default)] + #[serde(default = "default_proxy_secret_path")] pub proxy_secret_path: Option, /// Public IP override for middle-proxy NAT environments. @@ -157,15 +157,15 @@ pub struct GeneralConfig { pub middle_proxy_nat_ip: Option, /// Enable STUN-based NAT probing to discover public IP:port for ME KDF. - #[serde(default)] + #[serde(default = "default_true")] pub middle_proxy_nat_probe: bool, /// Optional STUN server address (host:port) for NAT probing. - #[serde(default)] + #[serde(default = "default_middle_proxy_nat_stun")] pub middle_proxy_nat_stun: Option, /// Optional list of STUN servers for NAT probing fallback. - #[serde(default)] + #[serde(default = "default_middle_proxy_nat_stun_servers")] pub middle_proxy_nat_stun_servers: Vec, /// Desired size of active Middle-Proxy writer pool. @@ -173,7 +173,7 @@ pub struct GeneralConfig { pub middle_proxy_pool_size: usize, /// Number of warm standby ME connections kept pre-initialized. - #[serde(default)] + #[serde(default = "default_middle_proxy_warm_standby")] pub middle_proxy_warm_standby: usize, /// Enable ME keepalive padding frames. @@ -207,7 +207,7 @@ pub struct GeneralConfig { pub desync_all_full: bool, /// Enable per-IP forensic observation buckets for scanners and handshake failures. - #[serde(default)] + #[serde(default = "default_true")] pub beobachten: bool, /// Observation retention window in minutes for per-IP forensic buckets. @@ -240,7 +240,7 @@ pub struct GeneralConfig { pub me_warmup_step_jitter_ms: u64, /// Max concurrent reconnect attempts per DC. - #[serde(default)] + #[serde(default = "default_me_reconnect_max_concurrent_per_dc")] pub me_reconnect_max_concurrent_per_dc: u32, /// Base backoff in ms for reconnect. @@ -252,7 +252,7 @@ pub struct GeneralConfig { pub me_reconnect_backoff_cap_ms: u64, /// Fast retry attempts before backoff. - #[serde(default)] + #[serde(default = "default_me_reconnect_fast_retry_count")] pub me_reconnect_fast_retry_count: u32, /// Ignore STUN/interface IP mismatch (keep using Middle Proxy even if NAT detected). @@ -280,7 +280,7 @@ pub struct GeneralConfig { /// Unified ME updater interval in seconds for getProxyConfig/getProxyConfigV6/getProxySecret. /// When omitted, effective value falls back to legacy proxy_*_auto_reload_secs fields. - #[serde(default)] + #[serde(default = "default_update_every")] pub update_every: Option, /// Periodic ME pool reinitialization interval in seconds. @@ -371,13 +371,13 @@ impl Default for GeneralConfig { modes: ProxyModes::default(), prefer_ipv6: false, fast_mode: default_true(), - use_middle_proxy: false, + use_middle_proxy: default_true(), ad_tag: None, - proxy_secret_path: None, + proxy_secret_path: default_proxy_secret_path(), middle_proxy_nat_ip: None, - middle_proxy_nat_probe: true, - middle_proxy_nat_stun: None, - middle_proxy_nat_stun_servers: Vec::new(), + middle_proxy_nat_probe: default_true(), + middle_proxy_nat_stun: default_middle_proxy_nat_stun(), + middle_proxy_nat_stun_servers: default_middle_proxy_nat_stun_servers(), middle_proxy_pool_size: default_pool_size(), middle_proxy_warm_standby: default_middle_proxy_warm_standby(), me_keepalive_enabled: default_true(), @@ -399,7 +399,7 @@ impl Default for GeneralConfig { crypto_pending_buffer: default_crypto_pending_buffer(), max_client_frame: default_max_client_frame(), desync_all_full: default_desync_all_full(), - beobachten: true, + beobachten: default_true(), beobachten_minutes: default_beobachten_minutes(), beobachten_flush_secs: default_beobachten_flush_secs(), beobachten_file: default_beobachten_file(), @@ -450,11 +450,11 @@ impl GeneralConfig { } /// `[general.links]` — proxy link generation settings. -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct LinksConfig { /// List of usernames whose tg:// links to display at startup. /// `"*"` = all users, `["alice", "bob"]` = specific users. - #[serde(default)] + #[serde(default = "default_links_show")] pub show: ShowLink, /// Public hostname/IP for tg:// link generation (overrides detected IP). @@ -466,15 +466,25 @@ pub struct LinksConfig { pub public_port: Option, } +impl Default for LinksConfig { + fn default() -> Self { + Self { + show: default_links_show(), + public_host: None, + public_port: None, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { #[serde(default = "default_port")] pub port: u16, - #[serde(default)] + #[serde(default = "default_listen_addr_ipv4")] pub listen_addr_ipv4: Option, - #[serde(default)] + #[serde(default = "default_listen_addr_ipv6_opt")] pub listen_addr_ipv6: Option, #[serde(default)] @@ -509,8 +519,8 @@ impl Default for ServerConfig { fn default() -> Self { Self { port: default_port(), - listen_addr_ipv4: Some(default_listen_addr()), - listen_addr_ipv6: Some(default_listen_addr_ipv6()), + listen_addr_ipv4: default_listen_addr_ipv4(), + listen_addr_ipv6: default_listen_addr_ipv6_opt(), listen_unix_sock: None, listen_unix_sock_perm: None, listen_tcp: None, @@ -583,7 +593,7 @@ pub struct AntiCensorshipConfig { pub fake_cert_len: usize, /// Enable TLS certificate emulation using cached real certificates. - #[serde(default)] + #[serde(default = "default_true")] pub tls_emulation: bool, /// Directory to store TLS front cache (on disk). @@ -636,7 +646,7 @@ impl Default for AntiCensorshipConfig { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AccessConfig { - #[serde(default)] + #[serde(default = "default_access_users")] pub users: HashMap, #[serde(default)] @@ -746,7 +756,7 @@ pub struct ListenerConfig { /// 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) +/// - omitted — default depends on the owning config field #[derive(Debug, Clone, Default)] pub enum ShowLink { /// Don't show any links (default when omitted). @@ -758,6 +768,10 @@ pub enum ShowLink { Specific(Vec), } +fn default_links_show() -> ShowLink { + ShowLink::All +} + impl ShowLink { /// Returns true if no links should be shown. pub fn is_empty(&self) -> bool { From da684b11fe86190e3cf055f10ef90b164dd59438 Mon Sep 17 00:00:00 2001 From: ivulit Date: Thu, 26 Feb 2026 13:36:33 +0300 Subject: [PATCH 2/5] feat: add mask_proxy_protocol option for PROXY protocol to mask_host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds mask_proxy_protocol config option (0 = off, 1 = v1 text, 2 = v2 binary) that sends a PROXY protocol header when connecting to mask_host. This lets the backend see the real client IP address. Particularly useful when the masking site (nginx/HAProxy) runs on the same host as telemt and listens on a local port — without this, the backend loses the original client IP entirely. PROXY protocol header is also sent during TLS emulation fetches so that backends with proxy_protocol required don't reject the connection. --- config.full.toml | 1 + src/config/types.rs | 7 ++++++ src/main.rs | 3 +++ src/proxy/client.rs | 16 +++++++------- src/proxy/masking.rs | 39 +++++++++++++++++++++++++++++---- src/tls_front/fetcher.rs | 26 +++++++++++++++++++--- src/transport/proxy_protocol.rs | 10 +++++---- 7 files changed, 83 insertions(+), 19 deletions(-) diff --git a/config.full.toml b/config.full.toml index 44db620..ac55167 100644 --- a/config.full.toml +++ b/config.full.toml @@ -135,6 +135,7 @@ mask = true # mask_host = "www.google.com" # example, defaults to tls_domain when both mask_host/mask_unix_sock are unset # mask_unix_sock = "/var/run/nginx.sock" # example, mutually exclusive with mask_host mask_port = 443 +# mask_proxy_protocol = 0 # Send PROXY protocol header to mask_host: 0 = off, 1 = v1 (text), 2 = v2 (binary) fake_cert_len = 2048 # if tls_emulation=false and default value is used, loader may randomize this value at runtime tls_emulation = true tls_front_dir = "tlsfront" diff --git a/src/config/types.rs b/src/config/types.rs index 1302a97..7703fe4 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -611,6 +611,12 @@ pub struct AntiCensorshipConfig { /// Enforce ALPN echo of client preference. #[serde(default = "default_alpn_enforce")] pub alpn_enforce: bool, + + /// Send PROXY protocol header when connecting to mask_host. + /// 0 = disabled, 1 = v1 (text), 2 = v2 (binary). + /// Allows the backend to see the real client IP. + #[serde(default)] + pub mask_proxy_protocol: u8, } impl Default for AntiCensorshipConfig { @@ -630,6 +636,7 @@ impl Default for AntiCensorshipConfig { tls_new_session_tickets: default_tls_new_session_tickets(), tls_full_cert_ttl_secs: default_tls_full_cert_ttl_secs(), alpn_enforce: default_alpn_enforce(), + mask_proxy_protocol: 0, } } } diff --git a/src/main.rs b/src/main.rs index c2b8c34..e4f7a79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -474,6 +474,7 @@ async fn main() -> std::result::Result<(), Box> { &domain, Duration::from_secs(5), Some(upstream_manager.clone()), + config.censorship.mask_proxy_protocol, ) .await { @@ -486,6 +487,7 @@ async fn main() -> std::result::Result<(), Box> { let cache_clone = cache.clone(); let domains = tls_domains.clone(); let upstream_for_task = upstream_manager.clone(); + let proxy_protocol = config.censorship.mask_proxy_protocol; tokio::spawn(async move { loop { let base_secs = rand::rng().random_range(4 * 3600..=6 * 3600); @@ -498,6 +500,7 @@ async fn main() -> std::result::Result<(), Box> { domain, Duration::from_secs(5), Some(upstream_for_task.clone()), + proxy_protocol, ) .await { diff --git a/src/proxy/client.rs b/src/proxy/client.rs index c598023..d8bbc48 100644 --- a/src/proxy/client.rs +++ b/src/proxy/client.rs @@ -143,7 +143,7 @@ where reader, writer, &first_bytes, - real_peer.ip(), + real_peer, &config, &beobachten, ) @@ -168,7 +168,7 @@ where reader, writer, &handshake, - real_peer.ip(), + real_peer, &config, &beobachten, ) @@ -212,7 +212,7 @@ where reader, writer, &first_bytes, - real_peer.ip(), + real_peer, &config, &beobachten, ) @@ -237,7 +237,7 @@ where reader, writer, &handshake, - real_peer.ip(), + real_peer, &config, &beobachten, ) @@ -462,7 +462,7 @@ impl RunningClientHandler { reader, writer, &first_bytes, - peer.ip(), + peer, &self.config, &self.beobachten, ) @@ -501,7 +501,7 @@ impl RunningClientHandler { reader, writer, &handshake, - peer.ip(), + peer, &config, &self.beobachten, ) @@ -570,7 +570,7 @@ impl RunningClientHandler { reader, writer, &first_bytes, - peer.ip(), + peer, &self.config, &self.beobachten, ) @@ -608,7 +608,7 @@ impl RunningClientHandler { reader, writer, &handshake, - peer.ip(), + peer, &config, &self.beobachten, ) diff --git a/src/proxy/masking.rs b/src/proxy/masking.rs index cdb6cf9..d12cf41 100644 --- a/src/proxy/masking.rs +++ b/src/proxy/masking.rs @@ -1,7 +1,7 @@ //! Masking - forward unrecognized traffic to mask host use std::str; -use std::net::IpAddr; +use std::net::SocketAddr; use std::time::Duration; use tokio::net::TcpStream; #[cfg(unix)] @@ -11,6 +11,7 @@ use tokio::time::timeout; use tracing::debug; use crate::config::ProxyConfig; use crate::stats::beobachten::BeobachtenStore; +use crate::transport::proxy_protocol::{ProxyProtocolV1Builder, ProxyProtocolV2Builder}; const MASK_TIMEOUT: Duration = Duration::from_secs(5); /// Maximum duration for the entire masking relay. @@ -52,7 +53,7 @@ pub async fn handle_bad_client( reader: R, writer: W, initial_data: &[u8], - peer_ip: IpAddr, + peer: SocketAddr, config: &ProxyConfig, beobachten: &BeobachtenStore, ) @@ -63,7 +64,7 @@ where let client_type = detect_client_type(initial_data); if config.general.beobachten { let ttl = Duration::from_secs(config.general.beobachten_minutes.saturating_mul(60)); - beobachten.record(client_type, peer_ip, ttl); + beobachten.record(client_type, peer.ip(), ttl); } if !config.censorship.mask { @@ -119,7 +120,37 @@ where let connect_result = timeout(MASK_TIMEOUT, TcpStream::connect(&mask_addr)).await; match connect_result { Ok(Ok(stream)) => { - let (mask_read, mask_write) = stream.into_split(); + let proxy_header: Option> = match config.censorship.mask_proxy_protocol { + 0 => None, + version => { + let header = if let Ok(local_addr) = stream.local_addr() { + match version { + 2 => ProxyProtocolV2Builder::new().with_addrs(peer, local_addr).build(), + _ => match (peer, local_addr) { + (SocketAddr::V4(src), SocketAddr::V4(dst)) => + ProxyProtocolV1Builder::new().tcp4(src.into(), dst.into()).build(), + (SocketAddr::V6(src), SocketAddr::V6(dst)) => + ProxyProtocolV1Builder::new().tcp6(src.into(), dst.into()).build(), + _ => + ProxyProtocolV1Builder::new().build(), + }, + } + } else { + match version { + 2 => ProxyProtocolV2Builder::new().build(), + _ => ProxyProtocolV1Builder::new().build(), + } + }; + Some(header) + } + }; + + let (mask_read, mut mask_write) = stream.into_split(); + if let Some(header) = proxy_header { + if mask_write.write_all(&header).await.is_err() { + return; + } + } if timeout(MASK_RELAY_TIMEOUT, relay_to_mask(reader, writer, mask_read, mask_write, initial_data)).await.is_err() { debug!("Mask relay timed out"); } diff --git a/src/tls_front/fetcher.rs b/src/tls_front/fetcher.rs index 7ac4b42..561d4cc 100644 --- a/src/tls_front/fetcher.rs +++ b/src/tls_front/fetcher.rs @@ -19,6 +19,7 @@ use x509_parser::certificate::X509Certificate; use crate::crypto::SecureRandom; use crate::protocol::constants::{TLS_RECORD_APPLICATION, TLS_RECORD_HANDSHAKE}; +use crate::transport::proxy_protocol::{ProxyProtocolV1Builder, ProxyProtocolV2Builder}; use crate::tls_front::types::{ ParsedCertificateInfo, ParsedServerHello, @@ -366,6 +367,7 @@ async fn fetch_via_raw_tls( port: u16, sni: &str, connect_timeout: Duration, + proxy_protocol: u8, ) -> Result { let addr = format!("{host}:{port}"); let mut stream = timeout(connect_timeout, TcpStream::connect(addr)).await??; @@ -373,6 +375,13 @@ async fn fetch_via_raw_tls( let rng = SecureRandom::new(); let client_hello = build_client_hello(sni, &rng); timeout(connect_timeout, async { + if proxy_protocol > 0 { + let header = match proxy_protocol { + 2 => ProxyProtocolV2Builder::new().build(), + _ => ProxyProtocolV1Builder::new().build(), + }; + stream.write_all(&header).await?; + } stream.write_all(&client_hello).await?; stream.flush().await?; Ok::<(), std::io::Error>(()) @@ -424,9 +433,10 @@ async fn fetch_via_rustls( sni: &str, connect_timeout: Duration, upstream: Option>, + proxy_protocol: u8, ) -> Result { // rustls handshake path for certificate and basic negotiated metadata. - let stream = if let Some(manager) = upstream { + let mut stream = if let Some(manager) = upstream { // Resolve host to SocketAddr if let Ok(mut addrs) = tokio::net::lookup_host((host, port)).await { if let Some(addr) = addrs.find(|a| a.is_ipv4()) { @@ -447,6 +457,15 @@ async fn fetch_via_rustls( timeout(connect_timeout, TcpStream::connect((host, port))).await?? }; + if proxy_protocol > 0 { + let header = match proxy_protocol { + 2 => ProxyProtocolV2Builder::new().build(), + _ => ProxyProtocolV1Builder::new().build(), + }; + stream.write_all(&header).await?; + stream.flush().await?; + } + let config = build_client_config(); let connector = TlsConnector::from(config); @@ -527,8 +546,9 @@ pub async fn fetch_real_tls( sni: &str, connect_timeout: Duration, upstream: Option>, + proxy_protocol: u8, ) -> Result { - let raw_result = match fetch_via_raw_tls(host, port, sni, connect_timeout).await { + let raw_result = match fetch_via_raw_tls(host, port, sni, connect_timeout, proxy_protocol).await { Ok(res) => Some(res), Err(e) => { warn!(sni = %sni, error = %e, "Raw TLS fetch failed"); @@ -536,7 +556,7 @@ pub async fn fetch_real_tls( } }; - match fetch_via_rustls(host, port, sni, connect_timeout, upstream).await { + match fetch_via_rustls(host, port, sni, connect_timeout, upstream, proxy_protocol).await { Ok(rustls_result) => { if let Some(mut raw) = raw_result { raw.cert_info = rustls_result.cert_info; diff --git a/src/transport/proxy_protocol.rs b/src/transport/proxy_protocol.rs index 770be7e..96f4ffb 100644 --- a/src/transport/proxy_protocol.rs +++ b/src/transport/proxy_protocol.rs @@ -233,14 +233,12 @@ async fn parse_v2( } /// Builder for PROXY protocol v1 header -#[allow(dead_code)] pub struct ProxyProtocolV1Builder { family: &'static str, src_addr: Option, dst_addr: Option, } -#[allow(dead_code)] impl ProxyProtocolV1Builder { pub fn new() -> Self { Self { @@ -288,13 +286,17 @@ impl Default for ProxyProtocolV1Builder { } /// Builder for PROXY protocol v2 header -#[allow(dead_code)] pub struct ProxyProtocolV2Builder { src: Option, dst: Option, } -#[allow(dead_code)] +impl Default for ProxyProtocolV2Builder { + fn default() -> Self { + Self::new() + } +} + impl ProxyProtocolV2Builder { pub fn new() -> Self { Self { src: None, dst: None } From fb1f85559ccb3edc1b9679217e14abde57bfcf61 Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:57:28 +0300 Subject: [PATCH 3/5] Update load.rs --- src/config/load.rs | 48 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/config/load.rs b/src/config/load.rs index be6759e..35099be 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -116,8 +116,27 @@ impl ProxyConfig { let base_dir = path.as_ref().parent().unwrap_or(Path::new(".")); let processed = preprocess_includes(&content, base_dir, 0)?; - let mut config: ProxyConfig = + let parsed_toml: toml::Value = toml::from_str(&processed).map_err(|e| ProxyError::Config(e.to_string()))?; + let general_table = parsed_toml + .get("general") + .and_then(|value| value.as_table()); + let update_every_is_explicit = general_table + .map(|table| table.contains_key("update_every")) + .unwrap_or(false); + let legacy_secret_is_explicit = general_table + .map(|table| table.contains_key("proxy_secret_auto_reload_secs")) + .unwrap_or(false); + let legacy_config_is_explicit = general_table + .map(|table| table.contains_key("proxy_config_auto_reload_secs")) + .unwrap_or(false); + + let mut config: ProxyConfig = + parsed_toml.try_into().map_err(|e| ProxyError::Config(e.to_string()))?; + + if !update_every_is_explicit && (legacy_secret_is_explicit || legacy_config_is_explicit) { + config.general.update_every = None; + } if let Some(update_every) = config.general.update_every { if update_every == 0 { @@ -437,15 +456,24 @@ mod tests { "#; let cfg: ProxyConfig = toml::from_str(toml).unwrap(); - assert_eq!(cfg.network.ipv6, None); - assert!(!cfg.network.stun_tcp_fallback); - assert_eq!(cfg.general.middle_proxy_warm_standby, 0); - assert_eq!(cfg.general.me_reconnect_max_concurrent_per_dc, 0); - assert_eq!(cfg.general.me_reconnect_fast_retry_count, 0); - assert_eq!(cfg.general.update_every, None); - assert_eq!(cfg.server.listen_addr_ipv4, None); - assert_eq!(cfg.server.listen_addr_ipv6, None); - assert!(cfg.access.users.is_empty()); + assert_eq!(cfg.network.ipv6, default_network_ipv6()); + assert_eq!(cfg.network.stun_tcp_fallback, default_stun_tcp_fallback()); + assert_eq!( + cfg.general.middle_proxy_warm_standby, + default_middle_proxy_warm_standby() + ); + assert_eq!( + cfg.general.me_reconnect_max_concurrent_per_dc, + default_me_reconnect_max_concurrent_per_dc() + ); + assert_eq!( + cfg.general.me_reconnect_fast_retry_count, + default_me_reconnect_fast_retry_count() + ); + assert_eq!(cfg.general.update_every, default_update_every()); + assert_eq!(cfg.server.listen_addr_ipv4, default_listen_addr_ipv4()); + assert_eq!(cfg.server.listen_addr_ipv6, default_listen_addr_ipv6_opt()); + assert_eq!(cfg.access.users, default_access_users()); } #[test] From d7182ae817674b32563a329640256b8b23765396 Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:07:04 +0300 Subject: [PATCH 4/5] Update defaults.rs --- src/config/defaults.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/defaults.rs b/src/config/defaults.rs index a2b2fc7..04755cb 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -7,7 +7,7 @@ const DEFAULT_NETWORK_IPV6: Option = Some(false); const DEFAULT_STUN_TCP_FALLBACK: bool = true; const DEFAULT_MIDDLE_PROXY_WARM_STANDBY: usize = 16; const DEFAULT_ME_RECONNECT_MAX_CONCURRENT_PER_DC: u32 = 8; -const DEFAULT_ME_RECONNECT_FAST_RETRY_COUNT: u32 = 11; +const DEFAULT_ME_RECONNECT_FAST_RETRY_COUNT: u32 = 16; const DEFAULT_LISTEN_ADDR_IPV6: &str = "::"; const DEFAULT_ACCESS_USER: &str = "default"; const DEFAULT_ACCESS_SECRET: &str = "00000000000000000000000000000000"; From e25b7f5ff8d99e527f9a9553b27fefdcfd258726 Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:10:21 +0300 Subject: [PATCH 5/5] STUN List --- src/config/defaults.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/config/defaults.rs b/src/config/defaults.rs index 04755cb..3fb8c3d 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -116,8 +116,19 @@ pub(crate) fn default_middle_proxy_nat_stun() -> Option { pub(crate) fn default_middle_proxy_nat_stun_servers() -> Vec { vec![ + "stun.l.google.com:5349".to_string(), + "stun1.l.google.com:3478".to_string(), + "stun.gmx.net:3478".to_string(), + "stun.l.google.com:19302".to_string(), + "stun.1und1.de:3478".to_string(), "stun1.l.google.com:19302".to_string(), "stun2.l.google.com:19302".to_string(), + "stun3.l.google.com:19302".to_string(), + "stun4.l.google.com:19302".to_string(), + "stun.services.mozilla.com:3478".to_string(), + "stun.stunprotocol.org:3478".to_string(), + "stun.nextcloud.com:3478".to_string(), + "stun.voip.eutelia.it:3478".to_string(), ] }