diff --git a/config.toml b/config.toml index f3df049..e82d97c 100644 --- a/config.toml +++ b/config.toml @@ -1,67 +1,66 @@ -# === General Settings === +# Telemt full config with default values. +# Examples are kept in comments after '#'. + +# Top-level legacy field. +show_link = [] # example: "*" or ["alice", "bob"] +# default_dc = 2 # example: default DC for unmapped non-standard DCs + [general] fast_mode = true -use_middle_proxy = true -# ad_tag = "00000000000000000000000000000000" -# Path to proxy-secret binary (auto-downloaded if missing). -proxy_secret_path = "proxy-secret" -# disable_colors = false # Disable colored output in logs (useful for files/systemd) - -# === Log Level === -# Log level: debug | verbose | normal | silent -# Can be overridden with --silent or --log-level CLI flags -# RUST_LOG env var takes absolute priority over all of these -log_level = "normal" - -# === Middle Proxy - ME === -# Public IP override for ME KDF when behind NAT; leave unset to auto-detect. -# middle_proxy_nat_ip = "203.0.113.10" -# Enable STUN probing to discover public IP:port for ME. +use_middle_proxy = false +# ad_tag = "0123456789abcdef0123456789abcdef" # example +# proxy_secret_path = "proxy-secret" # example custom path +# middle_proxy_nat_ip = "203.0.113.10" # example public NAT IP override middle_proxy_nat_probe = true -# Primary STUN server (host:port); defaults to Telegram STUN when empty. -middle_proxy_nat_stun = "stun.l.google.com:19302" -# Optional fallback STUN servers list. -middle_proxy_nat_stun_servers = ["stun1.l.google.com:19302", "stun2.l.google.com:19302"] -# Desired number of concurrent ME writers in pool. +# middle_proxy_nat_stun = "stun.l.google.com:19302" # example +middle_proxy_nat_stun_servers = [] # example: ["stun1.l.google.com:19302", "stun2.l.google.com:19302"] middle_proxy_pool_size = 8 -# Pre-initialized warm-standby ME connections kept idle. -middle_proxy_warm_standby = 8 -# Ignore STUN/interface mismatch and keep ME enabled even if IP differs. -stun_iface_mismatch_ignore = false -# Keepalive padding frames - fl==4 +middle_proxy_warm_standby = 16 me_keepalive_enabled = true -me_keepalive_interval_secs = 25 # Period between keepalives -me_keepalive_jitter_secs = 5 # Jitter added to interval -me_keepalive_payload_random = true # Randomize 4-byte payload (vs zeros) -# Stagger extra ME connections on warmup to de-phase lifecycles. +me_keepalive_interval_secs = 25 +me_keepalive_jitter_secs = 5 +me_keepalive_payload_random = true +crypto_pending_buffer = 262144 +max_client_frame = 16777216 +desync_all_full = false +beobachten = true +beobachten_minutes = 10 +beobachten_flush_secs = 15 +beobachten_file = "cache/beobachten.txt" +hardswap = true me_warmup_stagger_enabled = true -me_warmup_step_delay_ms = 500 # Base delay between extra connects -me_warmup_step_jitter_ms = 300 # Jitter for warmup delay -# Reconnect policy knobs. -me_reconnect_max_concurrent_per_dc = 4 # Parallel reconnects per DC - EXPERIMENTAL! UNSTABLE! -me_reconnect_backoff_base_ms = 500 # Backoff start -me_reconnect_backoff_cap_ms = 30000 # Backoff cap -me_reconnect_fast_retry_count = 11 # Quick retries before backoff -update_every = 7200 # Resolve the active updater interval for ME infrastructure refresh tasks. -crypto_pending_buffer = 262144 # Max pending ciphertext buffer per client writer (bytes). Controls FakeTLS backpressure vs throughput. -max_client_frame = 16777216 # Maximum allowed client MTProto frame size (bytes). -desync_all_full = false # Emit full crypto-desync forensic logs for every event. When false, full forensic details are emitted once per key window. -auto_degradation_enabled = true # Enable auto-degradation from ME to Direct-DC. -degradation_min_unavailable_dc_groups = 2 # Minimum unavailable ME DC groups before degrading. -hardswap = true # Enable C-like hard-swap for ME pool generations. When true, Telemt prewarms a new generation and switches once full coverage is reached. -default_me_hardswap_warmup_delay_min_ms = 1000 # Minimum delay in ms between hardswap warmup connect attempts. -default_me_hardswap_warmup_delay_max_ms = 2000 # Maximum delay in ms between hardswap warmup connect attempts. -default_me_hardswap_warmup_extra_passes = 3 # Additional warmup passes in the same hardswap cycle after the base pass. -default_me_hardswap_warmup_pass_backoff_base_ms = 500 # Base backoff in ms between hardswap warmup passes when floor is still incomplete. -me_pool_drain_ttl_secs = 90 # Drain-TTL in seconds for stale ME writers after endpoint map changes. During TTL, stale writers may be used only as fallback for new bindings. -me_pool_min_fresh_ratio = 0.8 # Minimum desired-DC coverage ratio required before draining stale writers. Range: 0.0..=1.0. -me_reinit_drain_timeout_secs = 120 # Drain timeout in seconds for stale ME writers after endpoint map changes. Set to 0 to keep stale writers draining indefinitely (no force-close). -me_config_stable_snapshots = 2 # Number of identical getProxyConfig snapshots required before applying ME map updates. -me_config_apply_cooldown_secs = 300 # Cooldown in seconds between applied ME map updates. -proxy_secret_rotate_runtime = true # Enable runtime proxy-secret rotation from getProxySecret. -proxy_secret_stable_snapshots = 2 # Number of identical getProxySecret snapshots required before runtime secret rotation. -proxy_secret_len_max = 256 # Maximum allowed proxy-secret length in bytes for startup and runtime refresh. -default_me_reinit_every_secs = 900 # Periodic ME pool reinitialization interval in seconds. +me_warmup_step_delay_ms = 500 +me_warmup_step_jitter_ms = 300 +me_reconnect_max_concurrent_per_dc = 8 +me_reconnect_backoff_base_ms = 500 +me_reconnect_backoff_cap_ms = 30000 +me_reconnect_fast_retry_count = 12 +stun_iface_mismatch_ignore = false +unknown_dc_log_path = "unknown-dc.txt" # to disable: set to null +log_level = "normal" # debug | verbose | normal | silent +disable_colors = false +fast_mode_min_tls_record = 0 +update_every = 300 +me_reinit_every_secs = 900 +me_hardswap_warmup_delay_min_ms = 1000 +me_hardswap_warmup_delay_max_ms = 2000 +me_hardswap_warmup_extra_passes = 3 +me_hardswap_warmup_pass_backoff_base_ms = 500 +me_config_stable_snapshots = 2 +me_config_apply_cooldown_secs = 300 +proxy_secret_stable_snapshots = 2 +proxy_secret_rotate_runtime = true +proxy_secret_len_max = 256 +me_pool_drain_ttl_secs = 90 +me_pool_min_fresh_ratio = 0.8 +me_reinit_drain_timeout_secs = 120 +# Legacy compatibility fields used when update_every is omitted. +proxy_secret_auto_reload_secs = 3600 +proxy_config_auto_reload_secs = 3600 +ntp_check = true +ntp_servers = ["pool.ntp.org"] # example: ["pool.ntp.org", "time.cloudflare.com"] +auto_degradation_enabled = true +degradation_min_unavailable_dc_groups = 2 [general.modes] classic = false @@ -69,63 +68,82 @@ secure = false tls = true [general.links] -show = "*" -# show = ["alice", "bob"] # Only show links for alice and bob -# show = "*" # Show links for all users -# public_host = "proxy.example.com" # Host (IP or domain) for tg:// links -# public_port = 443 # Port for tg:// links (default: server.port) +show = [] # example: "*" or ["alice", "bob"] +# public_host = "proxy.example.com" # example explicit host/IP for tg:// links +# public_port = 443 # example explicit port for tg:// links -# === Network Parameters === [network] -# Enable/disable families: true/false/auto(None) ipv4 = true -ipv6 = false # UNSTABLE WITH ME -# prefer = 4 or 6 -prefer = 4 -multipath = false # EXPERIMENTAL! +ipv6 = false # set true to enable IPv6 +prefer = 4 # 4 or 6 +multipath = false +stun_servers = [ + "stun.l.google.com:5349", + "stun1.l.google.com:3478", + "stun.gmx.net:3478", + "stun.l.google.com:19302", + "stun.1und1.de:3478", + "stun1.l.google.com:19302", + "stun2.l.google.com:19302", + "stun3.l.google.com:19302", + "stun4.l.google.com:19302", + "stun.services.mozilla.com:3478", + "stun.stunprotocol.org:3478", + "stun.nextcloud.com:3478", + "stun.voip.eutelia.it:3478", +] +stun_tcp_fallback = true +http_ip_detect_urls = ["https://ifconfig.me/ip", "https://api.ipify.org"] +cache_public_ip_path = "cache/public_ip.txt" -# === Server Binding === [server] port = 443 listen_addr_ipv4 = "0.0.0.0" listen_addr_ipv6 = "::" -# listen_unix_sock = "/var/run/telemt.sock" # Unix socket -# listen_unix_sock_perm = "0666" # Socket file permissions -# proxy_protocol = false # Enable if behind HAProxy/nginx with PROXY protocol -# metrics_port = 9090 -# metrics_whitelist = ["127.0.0.1", "::1"] +# listen_unix_sock = "/var/run/telemt.sock" # example +# listen_unix_sock_perm = "0660" # example unix socket mode +# listen_tcp = true # example explicit override (auto-detected when omitted) +proxy_protocol = false +# metrics_port = 9090 # example +metrics_whitelist = ["127.0.0.1/32", "::1/128"] +# Example explicit listeners (default: omitted, auto-generated from listen_addr_*): +# [[server.listeners]] +# ip = "0.0.0.0" +# announce = "proxy-v4.example.com" +# # announce_ip = "203.0.113.10" # deprecated alias +# proxy_protocol = false +# reuse_allow = false +# +# [[server.listeners]] +# ip = "::" +# announce = "proxy-v6.example.com" +# proxy_protocol = false +# reuse_allow = false -# Listen on multiple interfaces/IPs - IPv4 -[[server.listeners]] -ip = "0.0.0.0" - -# Listen on multiple interfaces/IPs - IPv6 -[[server.listeners]] -ip = "::" - -# === Timeouts (in seconds) === [timeouts] -client_handshake = 30 +client_handshake = 15 tg_connect = 10 client_keepalive = 60 client_ack = 300 -# Quick ME reconnects for single-address DCs (count and per-attempt timeout, ms). -me_one_retry = 12 -me_one_timeout_ms = 1200 +me_one_retry = 3 +me_one_timeout_ms = 1500 -# === Anti-Censorship & Masking === [censorship] tls_domain = "petrovich.ru" # tls_domains = ["example.com", "cdn.example.net"] # Additional domains for EE links 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_host = "petrovich.ru" # Defaults to tls_domain if not set -# mask_unix_sock = "/var/run/nginx.sock" # Unix socket (mutually exclusive with mask_host) -fake_cert_len = 2048 -# tls_emulation = false # Fetch real cert lengths and emulate TLS records -# tls_front_dir = "tlsfront" # Cache directory for TLS emulation +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" +server_hello_delay_min_ms = 0 +server_hello_delay_max_ms = 0 +tls_new_session_tickets = 0 +tls_full_cert_ttl_secs = 90 +alpn_enforce = true -# === Access Control & Users === [access] replay_check_len = 65536 replay_window_secs = 1800 @@ -134,34 +152,52 @@ ignore_time_skew = false [access.users] # format: "username" = "32_hex_chars_secret" hello = "00000000000000000000000000000000" +default = "00000000000000000000000000000000" +# alice = "11111111111111111111111111111111" # example -# [access.user_max_tcp_conns] -# hello = 50 +[access.user_max_tcp_conns] +# alice = 100 # example -# [access.user_max_unique_ips] -# hello = 5 +[access.user_expirations] +# alice = "2027-01-01T00:00:00Z" # example -# [access.user_data_quota] -# hello = 1073741824 # 1 GB +[access.user_data_quota] +# alice = 10737418240 # example bytes -# [access.user_expirations] -# format: username = "[year]-[month]-[day]T[hour]:[minute]:[second]Z" UTC -# hello = "2027-01-01T00:00:00Z" - -# === Upstreams & Routing === -[[upstreams]] -type = "direct" -enabled = true -weight = 10 -# interface = "192.168.1.100" # Bind outgoing to specific IP or iface name -# bind_addresses = ["192.168.1.100"] # List for round-robin binding (family must match target) +[access.user_max_unique_ips] +# alice = 10 # example +# Default behavior if [[upstreams]] is omitted: loader injects one direct upstream. +# Example explicit upstreams: +# [[upstreams]] +# type = "direct" +# interface = "eth0" +# bind_addresses = ["192.0.2.10"] +# weight = 1 +# enabled = true +# scopes = "*" +# +# [[upstreams]] +# type = "socks4" +# address = "198.51.100.20:1080" +# interface = "eth0" +# user_id = "telemt" +# weight = 1 +# enabled = true +# scopes = "*" +# # [[upstreams]] # type = "socks5" -# address = "127.0.0.1:1080" -# enabled = false +# address = "198.51.100.30:1080" +# interface = "eth0" +# username = "proxy-user" +# password = "proxy-pass" # weight = 1 +# enabled = true +# scopes = "*" # === DC Address Overrides === # [dc_overrides] -# "203" = "91.105.192.100:443" +# "201" = "149.154.175.50:443" # example +# "202" = ["149.154.167.51:443", "149.154.175.100:443"] # example +# "203" = "91.105.192.100:443" # loader auto-adds this one when omitted diff --git a/src/config/defaults.rs b/src/config/defaults.rs index 2ce7bac..51abe65 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -3,6 +3,15 @@ use ipnetwork::IpNetwork; use serde::Deserialize; // Helper defaults kept private to the config module. +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_LISTEN_ADDR_IPV6: &str = "::"; +const DEFAULT_ACCESS_USER: &str = "default"; +const DEFAULT_ACCESS_SECRET: &str = "00000000000000000000000000000000"; + pub(crate) fn default_true() -> bool { true } @@ -77,6 +86,14 @@ pub(crate) fn default_prefer_4() -> u8 { 4 } +pub(crate) fn default_network_ipv6() -> Option { + DEFAULT_NETWORK_IPV6 +} + +pub(crate) fn default_stun_tcp_fallback() -> bool { + DEFAULT_STUN_TCP_FALLBACK +} + pub(crate) fn default_unknown_dc_log_path() -> Option { Some("unknown-dc.txt".to_string()) } @@ -85,6 +102,10 @@ pub(crate) fn default_pool_size() -> usize { 8 } +pub(crate) fn default_middle_proxy_warm_standby() -> usize { + DEFAULT_MIDDLE_PROXY_WARM_STANDBY +} + pub(crate) fn default_keepalive_interval() -> u64 { 25 } @@ -109,6 +130,14 @@ pub(crate) fn default_reconnect_backoff_cap_ms() -> u64 { 30_000 } +pub(crate) fn default_me_reconnect_max_concurrent_per_dc() -> u32 { + DEFAULT_ME_RECONNECT_MAX_CONCURRENT_PER_DC +} + +pub(crate) fn default_me_reconnect_fast_retry_count() -> u32 { + DEFAULT_ME_RECONNECT_FAST_RETRY_COUNT +} + pub(crate) fn default_crypto_pending_buffer() -> usize { 256 * 1024 } @@ -191,7 +220,11 @@ pub(crate) fn default_proxy_config_reload_secs() -> u64 { } pub(crate) fn default_update_every_secs() -> u64 { - 30 * 60 + 5 * 60 +} + +pub(crate) fn default_update_every() -> Option { + Some(default_update_every_secs()) } pub(crate) fn default_me_reinit_every_secs() -> u64 { @@ -199,11 +232,11 @@ pub(crate) fn default_me_reinit_every_secs() -> u64 { } pub(crate) fn default_me_hardswap_warmup_delay_min_ms() -> u64 { - 2000 + 1000 } pub(crate) fn default_me_hardswap_warmup_delay_max_ms() -> u64 { - 3500 + 2000 } pub(crate) fn default_me_hardswap_warmup_extra_passes() -> u8 { @@ -266,6 +299,17 @@ pub(crate) fn default_degradation_min_unavailable_dc_groups() -> u8 { 2 } +pub(crate) fn default_listen_addr_ipv6() -> String { + DEFAULT_LISTEN_ADDR_IPV6.to_string() +} + +pub(crate) fn default_access_users() -> HashMap { + HashMap::from([( + DEFAULT_ACCESS_USER.to_string(), + DEFAULT_ACCESS_SECRET.to_string(), + )]) +} + // Custom deserializer helpers #[derive(Deserialize)] diff --git a/src/config/load.rs b/src/config/load.rs index aab553f..be6759e 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -427,6 +427,55 @@ impl ProxyConfig { mod tests { use super::*; + #[test] + fn serde_defaults_remain_unchanged_for_present_sections() { + let toml = r#" + [network] + [general] + [server] + [access] + "#; + 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()); + } + + #[test] + fn impl_defaults_are_sourced_from_default_helpers() { + let network = NetworkConfig::default(); + assert_eq!(network.ipv6, default_network_ipv6()); + assert_eq!(network.stun_tcp_fallback, default_stun_tcp_fallback()); + + let general = GeneralConfig::default(); + assert_eq!( + general.middle_proxy_warm_standby, + default_middle_proxy_warm_standby() + ); + assert_eq!( + general.me_reconnect_max_concurrent_per_dc, + default_me_reconnect_max_concurrent_per_dc() + ); + assert_eq!( + general.me_reconnect_fast_retry_count, + default_me_reconnect_fast_retry_count() + ); + assert_eq!(general.update_every, default_update_every()); + + let server = ServerConfig::default(); + assert_eq!(server.listen_addr_ipv6, Some(default_listen_addr_ipv6())); + + let access = AccessConfig::default(); + assert_eq!(access.users, default_access_users()); + } + #[test] fn dc_overrides_allow_string_and_array() { let toml = r#" diff --git a/src/config/types.rs b/src/config/types.rs index cfa8d31..1302a97 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -76,7 +76,7 @@ impl Default for ProxyModes { Self { classic: false, secure: false, - tls: true, + tls: default_true(), } } } @@ -117,12 +117,12 @@ pub struct NetworkConfig { impl Default for NetworkConfig { fn default() -> Self { Self { - ipv4: true, - ipv6: Some(false), - prefer: 4, + ipv4: default_true(), + ipv6: default_network_ipv6(), + prefer: default_prefer_4(), multipath: false, stun_servers: default_stun_servers(), - stun_tcp_fallback: true, + stun_tcp_fallback: default_stun_tcp_fallback(), http_ip_detect_urls: default_http_ip_detect_urls(), cache_public_ip_path: default_cache_public_ip_path(), } @@ -370,27 +370,27 @@ impl Default for GeneralConfig { Self { modes: ProxyModes::default(), prefer_ipv6: false, - fast_mode: true, + fast_mode: default_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_probe: true, middle_proxy_nat_stun: None, middle_proxy_nat_stun_servers: Vec::new(), middle_proxy_pool_size: default_pool_size(), - middle_proxy_warm_standby: 16, - me_keepalive_enabled: true, + middle_proxy_warm_standby: default_middle_proxy_warm_standby(), + me_keepalive_enabled: default_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_keepalive_payload_random: default_true(), + me_warmup_stagger_enabled: default_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: 8, + me_reconnect_max_concurrent_per_dc: default_me_reconnect_max_concurrent_per_dc(), 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: 8, + me_reconnect_fast_retry_count: default_me_reconnect_fast_retry_count(), stun_iface_mismatch_ignore: false, unknown_dc_log_path: default_unknown_dc_log_path(), log_level: LogLevel::Normal, @@ -399,13 +399,13 @@ 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: false, + beobachten: true, beobachten_minutes: default_beobachten_minutes(), beobachten_flush_secs: default_beobachten_flush_secs(), beobachten_file: default_beobachten_file(), hardswap: default_hardswap(), fast_mode_min_tls_record: default_fast_mode_min_tls_record(), - update_every: Some(default_update_every_secs()), + update_every: default_update_every(), me_reinit_every_secs: default_me_reinit_every_secs(), me_hardswap_warmup_delay_min_ms: default_me_hardswap_warmup_delay_min_ms(), me_hardswap_warmup_delay_max_ms: default_me_hardswap_warmup_delay_max_ms(), @@ -423,7 +423,7 @@ impl Default for GeneralConfig { proxy_config_auto_reload_secs: default_proxy_config_reload_secs(), ntp_check: default_ntp_check(), ntp_servers: default_ntp_servers(), - auto_degradation_enabled: true, + auto_degradation_enabled: default_true(), degradation_min_unavailable_dc_groups: default_degradation_min_unavailable_dc_groups(), } } @@ -510,7 +510,7 @@ impl Default for ServerConfig { Self { port: default_port(), listen_addr_ipv4: Some(default_listen_addr()), - listen_addr_ipv6: Some("::".to_string()), + listen_addr_ipv6: Some(default_listen_addr_ipv6()), listen_unix_sock: None, listen_unix_sock_perm: None, listen_tcp: None, @@ -618,12 +618,12 @@ impl Default for AntiCensorshipConfig { Self { tls_domain: default_tls_domain(), tls_domains: Vec::new(), - mask: true, + mask: default_true(), mask_host: None, mask_port: default_mask_port(), mask_unix_sock: None, fake_cert_len: default_fake_cert_len(), - tls_emulation: false, + tls_emulation: true, tls_front_dir: default_tls_front_dir(), server_hello_delay_min_ms: default_server_hello_delay_min_ms(), server_hello_delay_max_ms: default_server_hello_delay_max_ms(), @@ -663,13 +663,8 @@ pub struct AccessConfig { impl Default for AccessConfig { fn default() -> Self { - let mut users = HashMap::new(); - users.insert( - "default".to_string(), - "00000000000000000000000000000000".to_string(), - ); Self { - users, + users: default_access_users(), user_max_tcp_conns: HashMap::new(), user_expirations: HashMap::new(), user_data_quota: HashMap::new(),