From 4dc42bce4a49de5238241da166e8bd17befb5a4c Mon Sep 17 00:00:00 2001 From: ardocrat Date: Mon, 30 Mar 2026 01:37:36 +0300 Subject: [PATCH] tor: fix multiline bridge connection --- src/tor/tor.rs | 67 ++++++++++++++++++++++++++++++++---------------- src/tor/types.rs | 33 +++++++++++++++++------- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/tor/tor.rs b/src/tor/tor.rs index 04338d80..f48e9889 100644 --- a/src/tor/tor.rs +++ b/src/tor/tor.rs @@ -101,25 +101,32 @@ impl Default for Tor { impl Tor { /// Create Tor configuration returning unused bridges (exclude more than 2 to avoid stuck). - fn build_config(bridges: Option>) -> (TorClientConfig, Vec) { + fn build_config(bridges: Option>) + -> (TorClientConfig, Vec, Vec) { let mut builder = TorClientConfigBuilder::from_directories( TorConfig::state_path(), TorConfig::cache_path(), ); // Build bridges. - let bridges = bridges.unwrap_or(vec![]); + let mut bridges = bridges.unwrap_or(vec![]); let max_two_bridges = if bridges.len() > 2 { - bridges.iter().take(2).map(|b| b.clone()).collect::>() + let two_bridges = bridges.iter().take(2) + .cloned() + .collect::>(); + bridges = bridges.iter().filter(|b| !two_bridges.contains(b)) + .cloned() + .collect::>(); + two_bridges } else { bridges.clone() }; - for b in max_two_bridges { + for b in max_two_bridges.clone() { Self::build_bridge(&mut builder, b); } builder.address_filter().allow_onion_addrs(true); // Create config. let config = builder.build().unwrap(); - (config, bridges.iter().map(|b| b.clone()).collect::>()) + (config, bridges, max_two_bridges) } /// Build bootstrapped client from provided config. @@ -175,15 +182,14 @@ impl Tor { // Get initial bridges. let initial_bridges = if let Some(b) = TorConfig::get_bridge() { let lines_parse = serde_json::from_str::>(&b.connection_line()); - let bridges = if let Ok(lines) = lines_parse { - lines.iter() - .map(|l| TorBridge::Webtunnel(b.binary_path(), l.clone())) - .collect() - } else { - b.connection_line().lines() - .map(|l| TorBridge::Webtunnel(b.binary_path(), l.to_string())) - .collect() - }; + let bridges = lines_parse.unwrap_or_else(|_| b.connection_line() + .lines() + .map(|l| l.to_string()).collect() + ).iter().map(|l| { + let mut bridge = b.clone(); + bridge.update_conn_line(l.clone()); + bridge + }).collect::>(); Some(bridges) } else { None @@ -192,19 +198,39 @@ impl Tor { let mut default_attempt = false; let mut config_bridges = Self::build_config(initial_bridges); loop { - let (config, unused_bridges) = config_bridges.clone(); + let (config, unused_bridges, used_bridges) = config_bridges.clone(); let client = Self::build_client_bootstrap(config.clone()); if let Some(c) = client { + // Update bridges order. + if let Some(b) = TorConfig::get_bridge() { + let lines_parse = serde_json::from_str::>(&b.connection_line()); + match lines_parse { + Ok(mut lines) => { + lines.sort_by_key(|l| { + let mut bridge = b.clone(); + bridge.update_conn_line(l.clone()); + !used_bridges.contains(&bridge) + }); + let lines_str = serde_json::to_string(&lines) + .unwrap_or_else(|_| { + TorConfig::default_webtunnel_bridge().connection_line() + }); + TorBridge::save_bridge_conn_line(&b, lines_str); + } + Err(_) => {}, + } + } TOR_STATE.client_config.write().replace((c, config.clone())); break; } else { + // Cleanup state and cache. + fs::remove_dir_all(TorConfig::state_path()).unwrap_or_default(); + fs::remove_dir_all(TorConfig::cache_path()).unwrap_or_default(); + // Check unused bridges to check another part. if !unused_bridges.is_empty() { config_bridges = Self::build_config(Some(unused_bridges)); continue; } - // Cleanup state and cache. - fs::remove_dir_all(TorConfig::state_path()).unwrap_or_default(); - fs::remove_dir_all(TorConfig::cache_path()).unwrap_or_default(); if !default_attempt { default_attempt = true; // Launch client with default Webtunnel bridges if failed. @@ -214,11 +240,8 @@ impl Tor { config_bridges = Self::build_config(Some(add_bridges)); continue; } else if TorConfig::get_bridge().is_some() { - // Cleanup state and cache. - fs::remove_dir_all(TorConfig::state_path()).unwrap_or_default(); - fs::remove_dir_all(TorConfig::cache_path()).unwrap_or_default(); // Launch without bridges if all attempts failed. - let (config, _) = Self::build_config(None); + let (config, _, _) = Self::build_config(None); let client = Self::build_client_bootstrap(config.clone()); if let Some(c) = client { TOR_STATE.client_config.write().replace((c, config)); diff --git a/src/tor/types.rs b/src/tor/types.rs index b1bda024..921173f5 100644 --- a/src/tor/types.rs +++ b/src/tor/types.rs @@ -41,7 +41,7 @@ impl TorProxy { } /// Tor network bridge type. -#[derive(Serialize, Deserialize, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub enum TorBridge { /// Obfs4 bridge with binary path and connection line. Webtunnel(String, String), @@ -59,13 +59,13 @@ impl TorBridge { /// Default webtunnel protocol connection line. pub const DEFAULT_WEBTUNNEL_CONN_LINE: &'static str = "webtunnel [2001:db8:beb:5884:ffcc:bfe3:2858:b06b]:443 1E242C749707B4A68A269F0D31311CE36CDFEC28 url=https://wt.gri.mw/74Fm0lKUWWMMjZpKf6iSC0UH"; - pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_1: &'static str = "webtunnel [2001:db8:f1c4:ca39:40a2:2e3f:f66b:2308]:443 93557BF013203581B6B7C3BF016425F1758F7CD6 url=https://diffusesystems.net/UvVD4kzlcS8HLlpxDdRWXidiDTDt0EiZ ver=0.0.3"; - pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_2: &'static str = "webtunnel [2001:db8:eedb:cae7:a345:4f72:f9cc:5de0]:443 B3C81E7A0CA474270DAA4A2C8633E1CA8935C37D url=https://wordpress.far-east-investment.ru/sORes7268CEUSRD7hAWvJU5A ver=0.0.3"; - pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_3: &'static str = "webtunnel [2001:db8:945c:e0b9:7e4c:c974:ff00:d4c5]:443 91937F3EFB3BE5169788AC7C8BF07460B7E306DB url=https://kabel.entreri.de/YXbp1dNrJeOF8giAFFYWxvmf ver=0.0.3"; - pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_4: &'static str = "webtunnel [2001:db8:4767:7aa2:df21:1b2b:d7f9:caee]:443 CD193CF0D0C29551928C01FCB28D1200D9F27CFA url=https://occurrence.pics/68SzSlQCRgnfSo32eLyjC1V3 ver=0.0.3"; - pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_5: &'static str = "webtunnel [2001:db8:ce90:3593:272e:4975:a031:55b]:443 12382A2F3912AD1983A97C8709CBAE47ADB60BE3 url=https://miranda.today/LWwxIXDHCyyScn7oDauPMTmX ver=0.0.3"; - pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_6: &'static str = "webtunnel [2001:db8:a12b:ff8:8a1a:a05b:5f21:2ccc]:443 F2A9C5AEE0A420EB9D55F9497B3C0FA243A2A770 url=https://bridge.lovecloud.me/wss-wc3p0euqrlne98t9 ver=0.0.3"; - pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_7: &'static str = "webtunnel [2001:db8:8ed6:e6c9:5fc9:9f20:a373:2374]:443 1636A2EFFBAA4B162F5FF461A1663EB55C41AE11 url=https://hanoi.delivery/roQFPLtlspWT6yIKeXD6lEci ver=0.0.3"; + pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_1: &'static str = "webtunnel [2001:db8:1640:379c:ad30:db5f:bff5:37d0]:443 AF8F7548C886D6F53A652411DBB71D089517085A url=https://app05.oneclickhost.eu/alpfZGTB9FckCgOkOOA0OHlh"; + pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_2: &'static str = "webtunnel [2001:db8:eedb:cae7:a345:4f72:f9cc:5de0]:443 B3C81E7A0CA474270DAA4A2C8633E1CA8935C37D url=https://wordpress.far-east-investment.ru/sORes7268CEUSRD7hAWvJU5A"; + pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_3: &'static str = "webtunnel [2001:db8:945c:e0b9:7e4c:c974:ff00:d4c5]:443 91937F3EFB3BE5169788AC7C8BF07460B7E306DB url=https://kabel.entreri.de/YXbp1dNrJeOF8giAFFYWxvmf"; + pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_4: &'static str = "webtunnel [2001:db8:4767:7aa2:df21:1b2b:d7f9:caee]:443 CD193CF0D0C29551928C01FCB28D1200D9F27CFA url=https://occurrence.pics/68SzSlQCRgnfSo32eLyjC1V3"; + pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_5: &'static str = "webtunnel [2001:db8:ce90:3593:272e:4975:a031:55b]:443 12382A2F3912AD1983A97C8709CBAE47ADB60BE3 url=https://miranda.today/LWwxIXDHCyyScn7oDauPMTmX"; + pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_6: &'static str = "webtunnel [2001:db8:a12b:ff8:8a1a:a05b:5f21:2ccc]:443 F2A9C5AEE0A420EB9D55F9497B3C0FA243A2A770 url=https://bridge.lovecloud.me/wss-wc3p0euqrlne98t9"; + pub const ADDITIONAL_WEBTUNNEL_CONN_LINE_7: &'static str = "webtunnel [2001:db8:8ed6:e6c9:5fc9:9f20:a373:2374]:443 1636A2EFFBAA4B162F5FF461A1663EB55C41AE11 url=https://hanoi.delivery/roQFPLtlspWT6yIKeXD6lEci"; pub const DEFAULT_WEBTUNNEL_CONN_LINES: [&'static str; 8] = [ TorBridge::DEFAULT_WEBTUNNEL_CONN_LINE, @@ -79,7 +79,7 @@ impl TorBridge { ]; /// Default Obfs4 protocol connection line. - pub const DEFAULT_OBFS4_CONN_LINE: &'static str = "obfs4 45.76.43.226:3479 7AAFDC594147E72635DD64DB47A8CD8781F463F6 cert=bJ720bjXkmFGGAD77BsCMopkDzQ/cXDj0QntOmsBYw7Fqohq7Y7yZMV7FlECQNB1tyq1AA iat-mode=0"; + pub const DEFAULT_OBFS4_CONN_LINE: &'static str = "obfs4 51.83.248.35:25981 D08B4760D128C1A65506577E063D9D26C2A71815 cert=UJWUh+sIDdOKja/byBM2+qP9AFNl86hkGRFJ/lM1GWKP79eCu3PT4WTXI2gdXYULbQ0EMg iat-mode=0"; /// Default Snowflake protocol connection line. pub const DEFAULT_SNOWFLAKE_CONN_LINE: &'static str = "snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn"; @@ -106,6 +106,12 @@ impl TorBridge { } } + /// Get bridge client binary name. + pub fn binary_name(&self) -> String { + let path = self.binary_path(); + path.split(std::path::MAIN_SEPARATOR_STR).last().unwrap().to_string() + } + /// Get bridge client connection line. pub fn connection_line(&self) -> String { match self { @@ -115,6 +121,15 @@ impl TorBridge { } } + /// Update bridge connection line. + pub fn update_conn_line(&mut self, l: String) { + *self = match TorConfig::get_bridge().unwrap() { + TorBridge::Webtunnel(bin, _) => TorBridge::Webtunnel(bin, l.clone()), + TorBridge::Obfs4(bin, _) => TorBridge::Obfs4(bin, l.clone()), + TorBridge::Snowflake(bin, _) => TorBridge::Snowflake(bin, l.clone()), + }; + } + /// Save binary path to provided bridge. pub fn save_bridge_bin_path(bridge: &TorBridge, path: String) { match bridge {