From 804638193948dc4dccc8c53ae6f4fc40a58f77c1 Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Wed, 18 Feb 2026 06:01:08 +0300 Subject: [PATCH] [network] in main Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com> --- src/cli.rs | 9 +++- src/config/mod.rs | 71 +++++++++++++++++++++++++++ src/main.rs | 100 ++++++++++++++++++++------------------ src/proxy/direct_relay.rs | 4 +- 4 files changed, 134 insertions(+), 50 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 1440a63..25d14f0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -189,11 +189,18 @@ r#"# Telemt MTProxy — auto-generated config show_link = ["{username}"] [general] +# prefer_ipv6 is deprecated; use [network].prefer prefer_ipv6 = false fast_mode = true use_middle_proxy = false log_level = "normal" +[network] +ipv4 = true +ipv6 = true +prefer = 4 +multipath = false + [general.modes] classic = false secure = false @@ -297,4 +304,4 @@ fn print_links(username: &str, secret: &str, port: u16, domain: &str) { println!("The proxy will auto-detect and display the correct link on startup."); println!("Check: journalctl -u telemt.service | head -30"); println!("==================="); -} \ No newline at end of file +} diff --git a/src/config/mod.rs b/src/config/mod.rs index f9d2131..a2a3120 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -54,6 +54,10 @@ fn default_metrics_whitelist() -> Vec { vec!["127.0.0.1".parse().unwrap(), "::1".parse().unwrap()] } +fn default_prefer_4() -> u8 { + 4 +} + fn default_unknown_dc_log_path() -> Option { Some("unknown-dc.txt".to_string()) } @@ -185,6 +189,32 @@ impl std::fmt::Display for LogLevel { } } +fn validate_network_cfg(net: &mut NetworkConfig) -> Result<()> { + if !net.ipv4 && matches!(net.ipv6, Some(false)) { + return Err(ProxyError::Config( + "Both ipv4 and ipv6 are disabled in [network]".to_string(), + )); + } + + if net.prefer != 4 && net.prefer != 6 { + return Err(ProxyError::Config( + "network.prefer must be 4 or 6".to_string(), + )); + } + + if !net.ipv4 && net.prefer == 4 { + warn!("prefer=4 but ipv4=false; forcing prefer=6"); + net.prefer = 6; + } + + if matches!(net.ipv6, Some(false)) && net.prefer == 6 { + warn!("prefer=6 but ipv6=false; forcing prefer=4"); + net.prefer = 4; + } + + Ok(()) +} + // ============= Sub-Configs ============= #[derive(Debug, Clone, Serialize, Deserialize)] @@ -207,6 +237,34 @@ impl Default for ProxyModes { } } +#[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)] @@ -609,6 +667,9 @@ pub struct ProxyConfig { #[serde(default)] pub general: GeneralConfig, + #[serde(default)] + pub network: NetworkConfig, + #[serde(default)] pub server: ServerConfig, @@ -697,6 +758,16 @@ impl ProxyConfig { config.censorship.mask_host = Some(config.censorship.tls_domain.clone()); } + // Migration: prefer_ipv6 -> network.prefer + if config.general.prefer_ipv6 { + if config.network.prefer == 4 { + config.network.prefer = 6; + } + warn!("prefer_ipv6 is deprecated, use [network].prefer = 6"); + } + + validate_network_cfg(&mut config.network)?; + // Random fake_cert_len use rand::Rng; config.censorship.fake_cert_len = rand::rng().gen_range(1024..4096); diff --git a/src/main.rs b/src/main.rs index 2dd9a56..9865558 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ mod config; mod crypto; mod error; mod ip_tracker; +mod network; mod metrics; mod protocol; mod proxy; @@ -27,16 +28,14 @@ mod util; use crate::config::{LogLevel, ProxyConfig}; use crate::crypto::SecureRandom; use crate::ip_tracker::UserIpTracker; +use crate::network::probe::{decide_network_capabilities, log_probe_result, run_probe}; use crate::proxy::ClientHandler; use crate::stats::{ReplayChecker, Stats}; use crate::stream::BufferPool; use crate::transport::middle_proxy::{ MePool, fetch_proxy_config, run_me_ping, MePingFamily, MePingSample, format_sample_line, - stun_probe, }; use crate::transport::{ListenOptions, UpstreamManager, create_listener}; -use crate::util::ip::detect_ip; -use crate::protocol::constants::{TG_MIDDLE_PROXIES_V4, TG_MIDDLE_PROXIES_V6}; fn parse_cli() -> (String, bool, Option) { let mut config_path = "config.toml".to_string(); @@ -219,8 +218,17 @@ async fn main() -> std::result::Result<(), Box> { warn!("Using default tls_domain. Consider setting a custom domain."); } - let prefer_ipv6 = config.general.prefer_ipv6; - let mut use_middle_proxy = config.general.use_middle_proxy; + let probe = run_probe( + &config.network, + config.general.middle_proxy_nat_stun.clone(), + config.general.middle_proxy_nat_probe, + ) + .await?; + let decision = decide_network_capabilities(&config.network, &probe); + log_probe_result(&probe, &decision); + + let prefer_ipv6 = decision.prefer_ipv6(); + let mut use_middle_proxy = config.general.use_middle_proxy && (decision.ipv4_me || decision.ipv6_me); let config = Arc::new(config); let stats = Arc::new(Stats::new()); let rng = Arc::new(SecureRandom::new()); @@ -244,39 +252,9 @@ async fn main() -> std::result::Result<(), Box> { // Connection concurrency limit let _max_connections = Arc::new(Semaphore::new(10_000)); - // STUN check before choosing transport - if use_middle_proxy { - match stun_probe(config.general.middle_proxy_nat_stun.clone()).await { - Ok(Some(probe)) => { - info!( - local_ip = %probe.local_addr.ip(), - reflected_ip = %probe.reflected_addr.ip(), - "STUN Autodetect:" - ); - if probe.local_addr.ip() != probe.reflected_addr.ip() - && !config.general.stun_iface_mismatch_ignore - { - match crate::transport::middle_proxy::detect_public_ip().await { - Some(ip) => { - info!( - local_ip = %probe.local_addr.ip(), - reflected_ip = %probe.reflected_addr.ip(), - public_ip = %ip, - "STUN mismatch but public IP auto-detected, continuing with middle proxy" - ); - } - None => { - warn!( - "STUN/IP-on-Interface mismatch and public IP auto-detect failed -> fallback to direct-DC" - ); - use_middle_proxy = false; - } - } - } - } - Ok(None) => warn!("STUN probe returned no address; continuing"), - Err(e) => warn!(error = %e, "STUN probe failed; continuing"), - } + if use_middle_proxy && !decision.ipv4_me && !decision.ipv6_me { + warn!("No usable IP family for Middle Proxy detected; falling back to direct DC"); + use_middle_proxy = false; } // ===================================================================== @@ -351,6 +329,8 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai cfg_v4.map.clone(), cfg_v6.map.clone(), cfg_v4.default_dc.or(cfg_v6.default_dc), + decision.clone(), + rng.clone(), ); match pool.init(2, &rng).await { @@ -482,7 +462,12 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai info!("================= Telegram DC Connectivity ================="); let ping_results = upstream_manager - .ping_all_dcs(prefer_ipv6, &config.dc_overrides) + .ping_all_dcs( + prefer_ipv6, + &config.dc_overrides, + decision.ipv4_dc, + decision.ipv6_dc, + ) .await; for upstream_result in &ping_results { @@ -559,8 +544,15 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai // Background tasks let um_clone = upstream_manager.clone(); + let decision_clone = decision.clone(); tokio::spawn(async move { - um_clone.run_health_checks(prefer_ipv6).await; + um_clone + .run_health_checks( + prefer_ipv6, + decision_clone.ipv4_dc, + decision_clone.ipv6_dc, + ) + .await; }); let rc_clone = replay_checker.clone(); @@ -568,16 +560,31 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai rc_clone.run_periodic_cleanup().await; }); - let detected_ip = detect_ip().await; + let detected_ip_v4: Option = probe + .reflected_ipv4 + .map(|s| s.ip()) + .or_else(|| probe.detected_ipv4.map(std::net::IpAddr::V4)); + let detected_ip_v6: Option = probe + .reflected_ipv6 + .map(|s| s.ip()) + .or_else(|| probe.detected_ipv6.map(std::net::IpAddr::V6)); debug!( "Detected IPs: v4={:?} v6={:?}", - detected_ip.ipv4, detected_ip.ipv6 + detected_ip_v4, detected_ip_v6 ); let mut listeners = Vec::new(); for listener_conf in &config.server.listeners { let addr = SocketAddr::new(listener_conf.ip, config.server.port); + if addr.is_ipv4() && !decision.ipv4_dc { + warn!(%addr, "Skipping IPv4 listener: IPv4 disabled by [network]"); + continue; + } + if addr.is_ipv6() && !decision.ipv6_dc { + warn!(%addr, "Skipping IPv6 listener: IPv6 disabled by [network]"); + continue; + } let options = ListenOptions { ipv6_only: listener_conf.ip.is_ipv6(), ..Default::default() @@ -594,11 +601,11 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai } else if listener_conf.ip.is_unspecified() { // Auto-detect for unspecified addresses if listener_conf.ip.is_ipv4() { - detected_ip.ipv4 + detected_ip_v4 .map(|ip| ip.to_string()) .unwrap_or_else(|| listener_conf.ip.to_string()) } else { - detected_ip.ipv6 + detected_ip_v6 .map(|ip| ip.to_string()) .unwrap_or_else(|| listener_conf.ip.to_string()) } @@ -626,9 +633,8 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai let (host, port) = if let Some(ref h) = config.general.links.public_host { (h.clone(), config.general.links.public_port.unwrap_or(config.server.port)) } else { - let ip = detected_ip - .ipv4 - .or(detected_ip.ipv6) + let ip = detected_ip_v4 + .or(detected_ip_v6) .map(|ip| ip.to_string()); if ip.is_none() { warn!("show_link is configured but public IP could not be detected. Set public_host in config."); diff --git a/src/proxy/direct_relay.rs b/src/proxy/direct_relay.rs index ff50bca..537a93e 100644 --- a/src/proxy/direct_relay.rs +++ b/src/proxy/direct_relay.rs @@ -80,7 +80,8 @@ where } fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result { - let datacenters = if config.general.prefer_ipv6 { + let prefer_v6 = config.network.prefer == 6 && config.network.ipv6.unwrap_or(true); + let datacenters = if prefer_v6 { &*TG_DATACENTERS_V6 } else { &*TG_DATACENTERS_V4 @@ -90,7 +91,6 @@ fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result { let dc_key = dc_idx.to_string(); if let Some(addrs) = config.dc_overrides.get(&dc_key) { - let prefer_v6 = config.general.prefer_ipv6; let mut parsed = Vec::new(); for addr_str in addrs { match addr_str.parse::() {