diff --git a/src/config/defaults.rs b/src/config/defaults.rs index 4f0a53d..d82f8ed 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -111,25 +111,11 @@ pub(crate) fn default_proxy_secret_path() -> Option { } pub(crate) fn default_middle_proxy_nat_stun() -> Option { - Some("stun.l.google.com:19302".to_string()) + None } 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(), - ] + Vec::new() } pub(crate) fn default_stun_nat_probe_concurrency() -> usize { diff --git a/src/config/load.rs b/src/config/load.rs index 31a8b5d..0c1e629 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -65,6 +65,16 @@ fn validate_network_cfg(net: &mut NetworkConfig) -> Result<()> { Ok(()) } +fn push_unique_nonempty(target: &mut Vec, value: String) { + let trimmed = value.trim(); + if trimmed.is_empty() { + return; + } + if !target.iter().any(|existing| existing == trimmed) { + target.push(trimmed.to_string()); + } +} + // ============= Main Config ============= #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -138,6 +148,30 @@ impl ProxyConfig { config.general.update_every = None; } + let legacy_nat_stun = config.general.middle_proxy_nat_stun.take(); + let legacy_nat_stun_servers = std::mem::take(&mut config.general.middle_proxy_nat_stun_servers); + let legacy_nat_stun_used = legacy_nat_stun.is_some() || !legacy_nat_stun_servers.is_empty(); + + let mut unified_stun_servers = Vec::new(); + for stun in std::mem::take(&mut config.network.stun_servers) { + push_unique_nonempty(&mut unified_stun_servers, stun); + } + if let Some(stun) = legacy_nat_stun { + push_unique_nonempty(&mut unified_stun_servers, stun); + } + for stun in legacy_nat_stun_servers { + push_unique_nonempty(&mut unified_stun_servers, stun); + } + + if unified_stun_servers.is_empty() { + unified_stun_servers = default_stun_servers(); + } + config.network.stun_servers = unified_stun_servers; + + if legacy_nat_stun_used { + warn!("general.middle_proxy_nat_stun and general.middle_proxy_nat_stun_servers are deprecated; use network.stun_servers"); + } + if let Some(update_every) = config.general.update_every { if update_every == 0 { return Err(ProxyError::Config( diff --git a/src/config/types.rs b/src/config/types.rs index 58a3a3e..68086be 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -160,11 +160,13 @@ pub struct GeneralConfig { #[serde(default = "default_true")] pub middle_proxy_nat_probe: bool, - /// Optional STUN server address (host:port) for NAT probing. + /// Deprecated legacy single STUN server for NAT probing. + /// Use `network.stun_servers` instead. #[serde(default = "default_middle_proxy_nat_stun")] pub middle_proxy_nat_stun: Option, - /// Optional list of STUN servers for NAT probing fallback. + /// Deprecated legacy STUN list for NAT probing fallback. + /// Use `network.stun_servers` instead. #[serde(default = "default_middle_proxy_nat_stun_servers")] pub middle_proxy_nat_stun_servers: Vec, diff --git a/src/main.rs b/src/main.rs index dd4feb8..db46a6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -256,8 +256,6 @@ async fn main() -> std::result::Result<(), Box> { let probe = run_probe( &config.network, - config.general.middle_proxy_nat_stun.clone(), - config.general.middle_proxy_nat_stun_servers.clone(), config.general.middle_proxy_nat_probe, config.general.stun_nat_probe_concurrency, ) @@ -360,8 +358,8 @@ async fn main() -> std::result::Result<(), Box> { proxy_secret, config.general.middle_proxy_nat_ip, config.general.middle_proxy_nat_probe, - config.general.middle_proxy_nat_stun.clone(), - config.general.middle_proxy_nat_stun_servers.clone(), + None, + config.network.stun_servers.clone(), config.general.stun_nat_probe_concurrency, probe.detected_ipv6, config.timeouts.me_one_retry, diff --git a/src/network/probe.rs b/src/network/probe.rs index 378faa5..6e84682 100644 --- a/src/network/probe.rs +++ b/src/network/probe.rs @@ -57,8 +57,6 @@ const STUN_BATCH_TIMEOUT: Duration = Duration::from_secs(5); pub async fn run_probe( config: &NetworkConfig, - stun_addr: Option, - stun_servers: Vec, nat_probe: bool, stun_nat_probe_concurrency: usize, ) -> Result { @@ -71,12 +69,17 @@ pub async fn run_probe( probe.ipv6_is_bogon = probe.detected_ipv6.map(is_bogon_v6).unwrap_or(false); let stun_res = if nat_probe { - let servers = collect_stun_servers(config, stun_addr, stun_servers); - probe_stun_servers_parallel( - &servers, - stun_nat_probe_concurrency.max(1), - ) - .await + let servers = collect_stun_servers(config); + if servers.is_empty() { + warn!("STUN probe is enabled but network.stun_servers is empty"); + DualStunResult::default() + } else { + probe_stun_servers_parallel( + &servers, + stun_nat_probe_concurrency.max(1), + ) + .await + } } else { DualStunResult::default() }; @@ -143,36 +146,13 @@ async fn detect_public_ipv4_http(urls: &[String]) -> Option { None } -fn collect_stun_servers( - config: &NetworkConfig, - stun_addr: Option, - stun_servers: Vec, -) -> Vec { +fn collect_stun_servers(config: &NetworkConfig) -> Vec { let mut out = Vec::new(); - if !stun_servers.is_empty() { - for s in stun_servers { - if !s.is_empty() && !out.contains(&s) { - out.push(s); - } - } - } else if let Some(s) = stun_addr - && !s.is_empty() - { - out.push(s); - } - - if out.is_empty() { - for s in &config.stun_servers { - if !s.is_empty() && !out.contains(s) { - out.push(s.clone()); - } + for s in &config.stun_servers { + if !s.is_empty() && !out.contains(s) { + out.push(s.clone()); } } - - if out.is_empty() { - out.push("stun.l.google.com:19302".to_string()); - } - out } diff --git a/src/transport/middle_proxy/pool.rs b/src/transport/middle_proxy/pool.rs index c95457b..21c2b87 100644 --- a/src/transport/middle_proxy/pool.rs +++ b/src/transport/middle_proxy/pool.rs @@ -1199,6 +1199,35 @@ impl MePool { return false; } addrs.shuffle(&mut rand::rng()); + if addrs.len() > 1 { + let mut join = tokio::task::JoinSet::new(); + for (ip, port) in addrs { + let addr = SocketAddr::new(ip, port); + let pool = Arc::clone(&self); + let rng_clone = Arc::clone(&rng); + join.spawn(async move { (addr, pool.connect_one(addr, rng_clone.as_ref()).await) }); + } + + while let Some(res) = join.join_next().await { + match res { + Ok((addr, Ok(()))) => { + info!(%addr, dc = %dc, "ME connected"); + join.abort_all(); + while join.join_next().await.is_some() {} + return true; + } + Ok((addr, Err(e))) => { + warn!(%addr, dc = %dc, error = %e, "ME connect failed, trying next"); + } + Err(e) => { + warn!(dc = %dc, error = %e, "ME connect task failed"); + } + } + } + warn!(dc = %dc, "All ME servers for DC failed at init"); + return false; + } + for (ip, port) in addrs { let addr = SocketAddr::new(ip, port); match self.connect_one(addr, rng.as_ref()).await { diff --git a/src/transport/middle_proxy/pool_nat.rs b/src/transport/middle_proxy/pool_nat.rs index 37c0d5b..7141236 100644 --- a/src/transport/middle_proxy/pool_nat.rs +++ b/src/transport/middle_proxy/pool_nat.rs @@ -17,7 +17,15 @@ const STUN_BATCH_TIMEOUT: Duration = Duration::from_secs(5); #[allow(dead_code)] pub async fn stun_probe(stun_addr: Option) -> Result { - let stun_addr = stun_addr.unwrap_or_else(|| "stun.l.google.com:19302".to_string()); + let stun_addr = stun_addr.unwrap_or_else(|| { + crate::config::defaults::default_stun_servers() + .into_iter() + .next() + .unwrap_or_default() + }); + if stun_addr.is_empty() { + return Err(ProxyError::Proxy("STUN server is not configured".to_string())); + } stun_probe_dual(&stun_addr).await } @@ -31,10 +39,12 @@ impl MePool { if !self.nat_stun_servers.is_empty() { return self.nat_stun_servers.clone(); } - if let Some(s) = &self.nat_stun { + if let Some(s) = &self.nat_stun + && !s.trim().is_empty() + { return vec![s.clone()]; } - vec!["stun.l.google.com:19302".to_string()] + Vec::new() } async fn probe_stun_batch_for_family(