From ba0af0968d24ceed38113847e1b6f11d2294b516 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 18 Mar 2026 15:44:32 +0300 Subject: [PATCH] tor: multiline bridges input, optimize tor connection check, add multiple default webtunnel bridges, fix tx cancel on finalization error --- src/gui/views/settings/tor.rs | 68 ++- .../views/wallets/wallet/transport/content.rs | 4 +- .../wallets/wallet/transport/settings.rs | 2 +- src/gui/views/wallets/wallet/txs/content.rs | 13 +- src/tor/config.rs | 3 +- src/tor/tor.rs | 450 +++++++++++------- src/tor/types.rs | 19 + src/wallet/types.rs | 7 +- src/wallet/wallet.rs | 6 +- 9 files changed, 383 insertions(+), 189 deletions(-) diff --git a/src/gui/views/settings/tor.rs b/src/gui/views/settings/tor.rs index 8660f99..066d454 100644 --- a/src/gui/views/settings/tor.rs +++ b/src/gui/views/settings/tor.rs @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fs; -use egui::{Align, Id, Layout, RichText, StrokeKind}; use egui::os::OperatingSystem; +use egui::scroll_area::ScrollBarVisibility; +use egui::{Align, Id, Layout, RichText, ScrollArea, StrokeKind}; +use std::fs; use url::Url; -use crate::gui::icons::{CLOUD_CHECK, NOTCHES, PENCIL, SCAN, TERMINAL}; +use crate::gui::icons::{CLIPBOARD_TEXT, CLOUD_CHECK, NOTCHES, PENCIL, SCAN, TERMINAL}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::types::{ContentContainer, ModalPosition}; use crate::gui::views::{CameraScanContent, FilePickContent, FilePickContentType, Modal, TextEdit, View}; @@ -178,7 +179,6 @@ impl ContentContainer for TorSettingsContent { // Draw checkbox to enable/disable bridges. View::checkbox(ui, bridge.is_some(), t!("transport.bridges"), || { - // Save value. let value = if bridge.is_some() { None } else { @@ -431,7 +431,7 @@ impl TorSettingsContent { .color(Colors::gray())); ui.add_space(8.0); - // Draw p2p port text edit. + // Draw bridge text edit. let mut edit = TextEdit::new(Id::from(BRIDGE_BIN_EDIT_MODAL)).paste(); edit.ui(ui, &mut self.bridge_bin_path_edit, cb); if edit.enter_pressed { @@ -489,7 +489,7 @@ impl TorSettingsContent { // Show connection line edit modal. let title = bridge.protocol_name(); Modal::new(BRIDGE_CONN_LINE_EDIT_MODAL) - .position(ModalPosition::CenterTop) + .position(ModalPosition::Center) .title(title) .show(); }); @@ -541,12 +541,56 @@ impl TorSettingsContent { ui.add_space(8.0); // Draw connection line text edit. - let mut edit = TextEdit::new(Id::from(BRIDGE_CONN_LINE_EDIT_MODAL)).paste(); - edit.ui(ui, &mut self.bridge_conn_line_edit, cb); - if edit.enter_pressed { - on_save(self); - } - ui.add_space(12.0); + ui.vertical_centered(|ui| { + let scroll_id = Id::from(BRIDGE_CONN_LINE_EDIT_MODAL); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(3.0); + ScrollArea::both() + .id_salt(scroll_id) + .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) + .max_height(128.0) + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.add_space(7.0); + let input_id = scroll_id.with("_input"); + egui::TextEdit::multiline(&mut self.bridge_conn_line_edit) + .id(input_id) + .font(egui::TextStyle::Body) + .desired_rows(5) + .interactive(true) + .desired_width(f32::INFINITY) + .show(ui); + ui.add_space(6.0); + }); + }); + + ui.add_space(2.0); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(8.0); + + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + // Draw paste button. + let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste")); + View::button(ui, paste_text, Colors::white_or_black(false), || { + self.bridge_conn_line_edit = cb.get_string_from_buffer(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + // Draw button to scan bridge QR code. + let scan_text = format!("{} {}", SCAN, t!("scan")); + View::button(ui, scan_text, Colors::white_or_black(false), || { + self.show_qr_scan_bridge_modal(cb); + }); + }); + }); + + ui.add_space(8.0); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(8.0); // Show modal buttons. ui.scope(|ui| { diff --git a/src/gui/views/wallets/wallet/transport/content.rs b/src/gui/views/wallets/wallet/transport/content.rs index 35c531b..fc2e889 100644 --- a/src/gui/views/wallets/wallet/transport/content.rs +++ b/src/gui/views/wallets/wallet/transport/content.rs @@ -90,7 +90,7 @@ impl WalletTransportContent { pub fn back(&mut self) { if let Some(content) = self.settings_content.as_ref() { if content.tor_settings_content.settings_changed { - Tor::restart_services(); + Tor::restart(); } self.settings_content = None; } else if self.qr_address_content.is_some() { @@ -143,7 +143,7 @@ impl WalletTransportContent { if wallet.foreign_api_port().is_some() && wallet.secret_key().is_some() { let port = wallet.foreign_api_port().unwrap(); let key = wallet.secret_key().unwrap(); - if !Tor::is_service_starting(service_id) { + if !Tor::is_service_starting(service_id) { if !Tor::is_service_running(service_id) { let r = CornerRadius::default(); View::item_button(ui, r, POWER, Some(Colors::green()), || { diff --git a/src/gui/views/wallets/wallet/transport/settings.rs b/src/gui/views/wallets/wallet/transport/settings.rs index 8ec5014..30ed6eb 100644 --- a/src/gui/views/wallets/wallet/transport/settings.rs +++ b/src/gui/views/wallets/wallet/transport/settings.rs @@ -63,7 +63,7 @@ impl WalletTransportSettingsContent { ui.vertical_centered_justified(|ui| { View::button(ui, t!("close"), Colors::white_or_black(false), || { if self.tor_settings_content.settings_changed { - Tor::restart_services(); + Tor::restart(); } on_close(); }); diff --git a/src/gui/views/wallets/wallet/txs/content.rs b/src/gui/views/wallets/wallet/txs/content.rs index f4d5d93..ee726a1 100644 --- a/src/gui/views/wallets/wallet/txs/content.rs +++ b/src/gui/views/wallets/wallet/txs/content.rs @@ -485,23 +485,24 @@ impl WalletTransactionsContent { View::item_button(ui, rounding, icon, color, || { if repost { wallet.task(WalletTask::Post(tx.data.id)); - } else { - match tx.action.as_ref().unwrap() { - WalletTransactionAction::Cancelling => { - wallet.task(WalletTask::Cancel(tx.data.clone())); - } + } else if let Some(action) = tx.action.as_ref() { + match action { WalletTransactionAction::Finalizing => { wallet.task(WalletTask::Finalize(tx.data.id)); } WalletTransactionAction::Posting => { wallet.task(WalletTask::Post(tx.data.id)); } - WalletTransactionAction::SendingTor => { + _ => { if let Some(a) = &tx.receiver { wallet.task(WalletTask::SendTor(tx.data.id, a.clone())); } } } + } else { + if let Some(a) = &tx.receiver { + wallet.task(WalletTask::SendTor(tx.data.id, a.clone())); + } } }); } diff --git a/src/tor/config.rs b/src/tor/config.rs index 0269067..4a8312f 100644 --- a/src/tor/config.rs +++ b/src/tor/config.rs @@ -126,7 +126,8 @@ impl TorConfig { } else { TorConfig::webtunnel_path() }, - TorBridge::DEFAULT_WEBTUNNEL_CONN_LINE.to_string() + serde_json::to_string(&TorBridge::DEFAULT_WEBTUNNEL_CONN_LINES) + .unwrap_or(TorBridge::DEFAULT_WEBTUNNEL_CONN_LINE.to_string()) ) } diff --git a/src/tor/tor.rs b/src/tor/tor.rs index 0e6b885..495eae4 100644 --- a/src/tor/tor.rs +++ b/src/tor/tor.rs @@ -18,7 +18,6 @@ use arti_client::{TorClient, TorClientConfig}; use curve25519_dalek::digest::Digest; use ed25519_dalek::hazmat::ExpandedSecretKey; use fs_mistrust::Mistrust; -use futures::task::SpawnExt; use grin_util::secp::SecretKey; use http_body_util::{BodyExt, Full}; use lazy_static::lazy_static; @@ -30,6 +29,7 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use std::{fs, thread}; +use std::sync::atomic::{AtomicBool, Ordering}; use bytes::Bytes; use log::error; use safelog::DisplayRedacted; @@ -46,6 +46,7 @@ use tor_hsservice::{ }; use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector}; use tor_llcrypto::pk::ed25519::ExpandedKeypair; +use tor_rtcompat::SpawnExt; use tor_rtcompat::tokio::TokioNativeTlsRuntime; use crate::http::HttpClient; @@ -53,24 +54,25 @@ use crate::tor::http::ArtiHttpConnector; use crate::tor::{TorBridge, TorConfig, TorProxy}; lazy_static! { - /// Static thread-aware state of [`Node`] to be updated from separate thread. - static ref TOR_SERVER_STATE: Arc = Arc::new(Tor::default()); + /// Static thread-aware state of Tor to be updated from separate thread. + static ref TOR_STATE: Arc = Arc::new(Tor::default()); } -/// Tor server to use as SOCKS proxy for requests and to launch Onion services. +/// Tor client to use as SOCKS proxy for requests and to launch Onion services. pub struct Tor { /// Tor client and config. - client_config: Arc, TorClientConfig)>>, - /// Client to check services availability. - check_client: Arc>> - >>, + client_config: Arc, TorClientConfig)>>>, + /// Flag to check if client is launching. + client_launching: Arc, + /// Mapping of running Onion services identifiers to proxy. run: Arc, Arc)> >>, - /// Starting Onion services identifiers. - start: Arc>>, + /// Mapping of starting Onion services identifiers. + start: Arc + >>, /// Failed Onion services identifiers. fail: Arc>>, /// Checking Onion services identifiers. @@ -86,18 +88,11 @@ impl Default for Tor { fs::write(TorConfig::webtunnel_path(), webtunnel).unwrap_or_default(); } } - // Create Tor client. - let runtime = TokioNativeTlsRuntime::create().unwrap(); - let config = Self::build_config(true); - let client = TorClient::with_runtime(runtime) - .config(config.clone()) - .create_unbootstrapped() - .unwrap(); Self { - client_config: Arc::new(RwLock::new((client, config))), - check_client: Arc::new(RwLock::new(None)), + client_config: Arc::new(RwLock::new(None)), + client_launching: Arc::new(AtomicBool::new(false)), run: Arc::new(RwLock::new(BTreeMap::new())), - start: Arc::new(RwLock::new(BTreeSet::new())), + start: Arc::new(RwLock::new(BTreeMap::new())), fail: Arc::new(RwLock::new(BTreeSet::new())), check: Arc::new(RwLock::new(BTreeSet::new())), } @@ -106,29 +101,115 @@ impl Default for Tor { impl Tor { /// Create Tor client configuration. - fn build_config(clean: bool) -> TorClientConfig { - // Cleanup keys, state and cache. - if clean { - fs::remove_dir_all(TorConfig::keystore_path()).unwrap_or_default(); - fs::remove_dir_all(TorConfig::state_path()).unwrap_or_default(); - fs::remove_dir_all(TorConfig::cache_path()).unwrap_or_default(); - } - // Create Tor client config. + fn build_config(bridges: Option>) -> TorClientConfig { let mut builder = TorClientConfigBuilder::from_directories( TorConfig::state_path(), TorConfig::cache_path(), ); - builder.address_filter().allow_onion_addrs(true); - // Setup bridges. - let bridge = TorConfig::get_bridge(); - if let Some(b) = bridge { - Self::build_bridge(&mut builder, b); + // Build bridges. + if let Some(brs) = bridges { + for b in brs { + Self::build_bridge(&mut builder, b); + } } + builder.address_filter().allow_onion_addrs(true); // Create config. let config = builder.build().unwrap(); config } + /// Build bootstrapped client from provided config. + fn build_client_bootstrap(config: TorClientConfig) -> Option> { + let runtime = TokioNativeTlsRuntime::create().unwrap(); + let client_res = TorClient::with_runtime(runtime) + .config(config.clone()) + .create_unbootstrapped(); + if client_res.is_err() { + return None; + } + let client = client_res.unwrap(); + let bootstrapping = Arc::new(AtomicBool::new(true)); + let bootstrap_success = Arc::new(AtomicBool::new(false)); + let bootstrapping_t = bootstrapping.clone(); + let bootstrap_success_t = bootstrap_success.clone(); + let c = client.clone(); + client.runtime().spawn(async move { + let task = c.bootstrap(); + // Bootstrap client with 60s timeout. + if tokio::time::timeout(Duration::from_millis(60000), task).await.is_ok() { + bootstrap_success_t.store(true, Ordering::Relaxed); + } + bootstrapping_t.store(false, Ordering::Relaxed); + }).unwrap(); + // Wait client to finish bootstrap. + while bootstrapping.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(1000)); + } + if bootstrap_success.load(Ordering::Relaxed) { + Some(client) + } else { + None + } + } + + /// Launch Tor client. + fn launch() { + // Wait client to finish launch and exit on success. + while TOR_STATE.client_launching.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(1000)); + } + { + if TOR_STATE.client_config.read().is_some() { + return; + } + } + TOR_STATE.client_launching.store(true, Ordering::Relaxed); + // Setup config. + let config = 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() + }; + Self::build_config(Some(bridges)) + } else { + Self::build_config(None) + }; + // Launch client. + let client = Self::build_client_bootstrap(config.clone()); + if let Some(c) = client { + TOR_STATE.client_config.write().replace((c, config)); + thread::sleep(Duration::from_millis(5000)); + TOR_STATE.client_launching.store(false, Ordering::Relaxed); + } else { + // Launch client with default Webtunnel bridges if failed. + let add_bridges = TorBridge::DEFAULT_WEBTUNNEL_CONN_LINES.iter() + .map(|b| TorBridge::Webtunnel(TorConfig::webtunnel_path(), b.to_string())) + .collect::>(); + let config = Self::build_config(Some(add_bridges)); + let client = Self::build_client_bootstrap(config.clone()); + if let Some(c) = client { + TOR_STATE.client_config.write().replace((c, config)); + thread::sleep(Duration::from_millis(5000)); + TOR_STATE.client_launching.store(false, Ordering::Relaxed); + } else if TorConfig::get_bridge().is_some() { + // Launch without bridges if all attempts failed. + 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)); + thread::sleep(Duration::from_millis(5000)); + } + } + } + TOR_STATE.client_launching.store(false, Ordering::Relaxed); + } + /// Send post request using Tor. pub async fn post(body: String, url: String) -> Option { if let Some(proxy) = TorConfig::get_proxy() { @@ -172,13 +253,18 @@ impl Tor { } else { if let Some(b) = TorConfig::get_bridge() { if !fs::exists(b.binary_path()).unwrap() { + error!("Tor: bridge binary not exists"); return None; } } - // Bootstrap client. - let (client, _) = Self::client_config(); - client.bootstrap().await.unwrap_or_default(); + // Launch client if needed. + Self::launch(); + if Self::client_config().is_none() { + error!("Tor: can not launch Tor client"); + return None; + } // Create http tor-powered client to post data. + let client = Self::client_config().unwrap().0.isolated_client(); let tls_conn = TlsConnector::builder().unwrap().build().unwrap(); let conn = ArtiHttpConnector::new(client, tls_conn); let http = hyper_tor::Client::builder().build::<_, hyper_tor::Body>(conn); @@ -205,52 +291,82 @@ impl Tor { } } - fn client_config() -> (TorClient, TorClientConfig) { - let r_client_config = TOR_SERVER_STATE.client_config.read(); + fn client_config() -> Option<(TorClient, TorClientConfig)> { + let r_client_config = TOR_STATE.client_config.read(); r_client_config.clone() } /// Check if Onion service is starting. pub fn is_service_starting(id: &String) -> bool { - let r_services = TOR_SERVER_STATE.start.read(); - r_services.contains(id) + let r_services = TOR_STATE.start.read(); + r_services.contains_key(id) } /// Check if Onion service is running. pub fn is_service_running(id: &String) -> bool { - let r_services = TOR_SERVER_STATE.run.read(); + let r_services = TOR_STATE.run.read(); r_services.contains_key(id) } /// Check if Onion service failed on start. pub fn is_service_failed(id: &String) -> bool { - let r_services = TOR_SERVER_STATE.fail.read(); + let r_services = TOR_STATE.fail.read(); r_services.contains(id) } /// Check if Onion service is checking. pub fn is_service_checking(id: &String) -> bool { - let r_services = TOR_SERVER_STATE.check.read(); + let r_services = TOR_STATE.check.read(); r_services.contains(id) } + /// Restart Tor client at separate thread. + pub fn restart() { + thread::spawn(|| { + // Exit if client was not launched. + if Self::client_config().is_none() { + return; + } + Self::restart_services(); + }); + } + /// Restart running Onion services. - pub fn restart_services() { - // Stop all services saving port key for relaunch. + fn restart_services() { + // Stop all services saving keys to relaunch. let service_ids = { - let r_services = TOR_SERVER_STATE.run.read().clone(); + let r_services = TOR_STATE.run.read().clone(); r_services.keys().map(|s| s.to_string()).collect::>() }; - let mut services: BTreeMap = BTreeMap::new(); + let mut services: BTreeMap = TOR_STATE.start.read().clone(); for id in service_ids.clone() { if let Some(res) = Self::stop_service(&id) { services.insert(id, res); } } - // Reconfigure client. - let config = Self::build_config(false); - let r_client = TOR_SERVER_STATE.client_config.read(); - r_client.0.reconfigure(&config, tor_config::Reconfigure::WarnOnFailures).unwrap(); + // Put stopped services to start. + { + let mut w_services = TOR_STATE.start.write(); + *w_services = services.clone(); + } + // Cleanup keys, state and cache. + fs::remove_dir_all(TorConfig::keystore_path()).unwrap_or_default(); + fs::remove_dir_all(TorConfig::state_path()).unwrap_or_default(); + fs::remove_dir_all(TorConfig::cache_path()).unwrap_or_default(); + { + let mut w_client = TOR_STATE.client_config.write(); + *w_client = None; + } + // Relaunch client. + Self::launch(); + // Save failed services if client was not created. + if Self::client_config().is_none() { + for id in service_ids { + let mut w_services = TOR_STATE.fail.write(); + w_services.insert(id); + } + return; + } // Start services. for id in services.keys() { let (port, key) = services.get(id).unwrap(); @@ -260,43 +376,62 @@ impl Tor { /// Stop running Onion service returning port and key. pub fn stop_service(id: &String) -> Option<(u16, SecretKey)> { + let mut port_key = None; { - // Remove service from starting. - let mut w_services = TOR_SERVER_STATE.start.write(); + // Remove service from checking. + let mut w_services = TOR_STATE.check.write(); w_services.remove(id); } - let mut w_services = TOR_SERVER_STATE.run.write(); - if let Some((port, key, svc, proxy)) = w_services.remove(id) { - proxy.shutdown(); - drop(proxy); - drop(svc); - return Some((port, key)); + // Remove service from starting. + { + let mut w_services = TOR_STATE.start.write(); + if let Some((port, key)) = w_services.remove(id) { + port_key = Some((port, key)); + } } - None + // Remove service from running. + { + let mut w_services = TOR_STATE.run.write(); + if let Some((port, key, svc, proxy)) = w_services.remove(id) { + proxy.shutdown(); + drop(proxy); + drop(svc); + port_key = Some((port, key)); + } + } + // Remove client when no running services left. + if TOR_STATE.start.read().is_empty() && TOR_STATE.run.read().is_empty() { + let mut w_client = TOR_STATE.client_config.write(); + *w_client = None; + // Clear state. + fs::remove_dir_all(TorConfig::state_path()).unwrap_or_default();} + + port_key } /// Start Onion service from listening local port and [`SecretKey`]. pub fn start_service(port: u16, key: SecretKey, id: &String) { // Check if service is already running. - if Self::is_service_running(id) || Self::is_service_starting(id) { + if Self::is_service_running(id) { return; - } else { - // Save starting service. - let mut w_services = TOR_SERVER_STATE.start.write(); - w_services.insert(id.clone()); - // Remove service from failed. - let mut w_services = TOR_SERVER_STATE.fail.write(); - w_services.remove(id); } - let service_id = id.clone(); thread::spawn(move || { + { + // Save starting service. + let mut w_services = TOR_STATE.start.write(); + w_services.insert(service_id.clone(), (port, key.clone())); + // Remove service from failed. + let mut w_services = TOR_STATE.fail.write(); + w_services.remove(&service_id); + } + let on_error = |service_id: String| { // Remove service from starting. - let mut w_services = TOR_SERVER_STATE.start.write(); + let mut w_services = TOR_STATE.start.write(); w_services.remove(&service_id); // Save failed service. - let mut w_services = TOR_SERVER_STATE.fail.write(); + let mut w_services = TOR_STATE.fail.write(); w_services.insert(service_id); }; @@ -319,7 +454,14 @@ impl Tor { } } - let (client, config) = Self::client_config(); + // Launch client if not exists. + Self::launch(); + let client_config = Self::client_config(); + if client_config.is_none() { + on_error(service_id); + return; + } + let (client, config) = client_config.unwrap(); client .runtime() .spawn(async move { @@ -329,40 +471,34 @@ impl Tor { on_error(service_id); return; } - let (c, _) = Self::client_config(); - // Bootstrap client. - if c.bootstrap_status().as_frac() == 0.0 { - if let Err(_) = c.bootstrap().await { - on_error(service_id); - return; - } - // Launch first time after delay. - thread::sleep(Duration::from_millis(10000)); - } // Launch Onion service. let service_config = OnionServiceConfigBuilder::default() .nickname(hs.clone()) .build() .unwrap(); + let client_config = Self::client_config(); + if client_config.is_none() { + on_error(service_id.clone()); + } + let c = client_config.unwrap().0.isolated_client(); if let Ok(res) = c.launch_onion_service(service_config) { if let Some((service, request)) = res { - let onion_addr = service.onion_address(); // Launch service proxy. let addr = SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), port); - let proxy = tokio::spawn(Self::run_service_proxy( - addr, - request, - hs.clone(), - )).await.unwrap(); + let run = Self::run_service_proxy(c, addr, request, hs.clone()); + let proxy = tokio::spawn(run).await.unwrap(); + let onion_addr = service.onion_address(); // Save running service. - let mut w_services = TOR_SERVER_STATE.run.write(); - let id = service_id.clone(); - w_services.insert(id, (port, key.clone(), service, proxy)); + { + let mut w_services = TOR_STATE.run.write(); + let id = service_id.clone(); + w_services.insert(id, (port, key.clone(), service, proxy)); + } // Check service availability. let addr = onion_addr.unwrap().display_unredacted().to_string(); let url = format!("http://{}/", addr); if !Self::is_service_checking(&service_id) { - Self::check_service(service_id, url, port, key) + Self::check_service(service_id, url) } return; } @@ -373,93 +509,86 @@ impl Tor { }); } + /// Check is service is running on check. + fn check_running(service_id: &String) -> bool { + let running = Tor::is_service_running(service_id) && Self::client_config().is_some(); + if !running { + // Remove service from checking. + let mut w_services = TOR_STATE.check.write(); + w_services.remove(service_id); + } + running + } + /// Check service availability. - fn check_service(service_id: String, - url: String, - port: u16, - key: SecretKey) { + fn check_service(service_id: String, url: String) { { - let mut w_services = TOR_SERVER_STATE.check.write(); + let mut w_services = TOR_STATE.check.write(); w_services.insert(service_id.clone()); } - let (client, _) = Self::client_config(); thread::spawn(move || { - // Wait 5 sec before check. - thread::sleep(Duration::from_millis(5000)); - // Request client setup. - if TOR_SERVER_STATE.check_client.read().is_none() { - let (client_check, _) = Self::client_config(); - let tls_conn = TlsConnector::builder().unwrap().build().unwrap(); - let conn = ArtiHttpConnector::new(client_check, tls_conn); - let http = hyper_tor::Client::builder().build::<_, hyper_tor::Body>(conn); - let mut w_client = TOR_SERVER_STATE.check_client.write(); - *w_client = Some(http); - } - let r_client = TOR_SERVER_STATE.check_client.read(); - let http = r_client.clone().unwrap(); - let runtime = client.runtime(); - runtime - .spawn(async move { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { const MAX_ERRORS: i32 = 16; let mut errors_count = 0; + // Wait 5 seconds. + thread::sleep(Duration::from_millis(5000)); loop { - // Check if service is running. - fn is_running(service_id: &String) -> bool { - let running = Tor::is_service_running(service_id); - if !running { - // Remove service from checking. - let mut w_services = - TOR_SERVER_STATE.check.write(); - w_services.remove(service_id); - } - running - } - if !is_running(&service_id) { + if !Self::check_running(&service_id) { break; } let duration = { + // Send request. + let tls_conn = TlsConnector::builder().unwrap().build().unwrap(); + let client_config = Self::client_config(); + if client_config.is_none() { + return; + } + let client = client_config.unwrap().0.isolated_client(); + let conn = ArtiHttpConnector::new(client, tls_conn); + let http = hyper_tor::Client::builder() + .build::<_, hyper_tor::Body>(conn); + let uri = hyper_tor::Uri::from_str(url.clone().as_str()).unwrap(); + let check = http.get(uri.clone()); + // Setup error callback. let mut on_error = |service_id: &String| -> bool { - if !is_running(service_id) { + if !Self::check_running(service_id) { return true; } // Restart service after maximum amount of errors. errors_count += 1; - if errors_count == MAX_ERRORS { - // Remove service from checking. - let mut w_services = - TOR_SERVER_STATE.check.write(); - w_services.remove(service_id); - // Remove service from starting. - let mut w_services = TOR_SERVER_STATE.start.write(); - w_services.remove(service_id); - // Restart service. - let key = key.clone(); - let id = service_id.clone(); - thread::spawn(move || { - Self::stop_service(&id); - Self::start_service(port, key, &id); - }); - return true; + let max_errors = errors_count >= MAX_ERRORS; + if max_errors { + { + // Remove service from checking. + let mut w_services = TOR_STATE.check.write(); + w_services.remove(service_id); + // Remove service from starting. + let mut w_services = TOR_STATE.start.write(); + w_services.remove(service_id); + } + // Restart services. + Self::restart(); } - false + max_errors }; - // Send request. - let uri = hyper_tor::Uri::from_str(url.clone().as_str()).unwrap(); - let check = http.get(uri); - // Check with timeout of 20s. - match tokio::time::timeout(Duration::from_millis(20000), check).await { + // Check with timeout of 30s. + match tokio::time::timeout(Duration::from_millis(30000), check).await { Ok(resp) => { match resp { Ok(_) => { - if !is_running(&service_id) { + if !Self::check_running(&service_id) { break; } // Remove service from starting. - let mut w_services = TOR_SERVER_STATE.start.write(); + let mut w_services = TOR_STATE.start.write(); w_services.remove(&service_id); errors_count = 0; - // Check again after 20s. - Duration::from_millis(20000) + // Check again after 60s. + Duration::from_millis(60000) } Err(e) => { if on_error(&service_id) { @@ -486,13 +615,13 @@ impl Tor { // Wait to check service again. thread::sleep(duration); } - }) - .unwrap(); + }); }); } /// Launch Onion service proxy. async fn run_service_proxy( + client: TorClient, addr: SocketAddr, request: S, nickname: HsNickname, @@ -500,7 +629,6 @@ impl Tor { S: futures::Stream + Unpin + Send + 'static, { let id = nickname.to_string(); - let (client, _) = Self::client_config(); let runtime = client.runtime().clone(); // Setup proxy to forward request from Tor address to local address. @@ -513,7 +641,7 @@ impl Tor { let proxy = OnionServiceReverseProxy::new(proxy_cfg_builder.build().unwrap()); // Remove service from failed. - let mut w_services = TOR_SERVER_STATE.fail.write(); + let mut w_services = TOR_STATE.fail.write(); w_services.remove(&id); // Start proxy for launched service. @@ -524,16 +652,16 @@ impl Tor { match p.handle_requests(runtime, nickname.clone(), request).await { Ok(()) => { // Remove service from running. - let mut w_services = TOR_SERVER_STATE.run.write(); + let mut w_services = TOR_STATE.run.write(); w_services.remove(&id); } Err(_) => { if Self::is_service_running(&id) { // Remove service from running. - let mut w_services = TOR_SERVER_STATE.run.write(); + let mut w_services = TOR_STATE.run.write(); w_services.remove(&id); // Save failed service. - let mut w_services = TOR_SERVER_STATE.fail.write(); + let mut w_services = TOR_STATE.fail.write(); w_services.insert(id); } } diff --git a/src/tor/types.rs b/src/tor/types.rs index 049a3c6..e7a5e03 100644 --- a/src/tor/types.rs +++ b/src/tor/types.rs @@ -59,6 +59,25 @@ 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:4c9a:ffe8:70f8:d5af:a8f1:cf56]:443 DA1ECF055635C1A6ED7F5B5F36296A5E3015CE57 url=https://1axfa6xb.xoomlia.com/6qtxxkjw/ 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 DEFAULT_WEBTUNNEL_CONN_LINES: [&'static str; 8] = [ + TorBridge::DEFAULT_WEBTUNNEL_CONN_LINE, + TorBridge::ADDITIONAL_WEBTUNNEL_CONN_LINE_1, + TorBridge::ADDITIONAL_WEBTUNNEL_CONN_LINE_2, + TorBridge::ADDITIONAL_WEBTUNNEL_CONN_LINE_3, + TorBridge::ADDITIONAL_WEBTUNNEL_CONN_LINE_4, + TorBridge::ADDITIONAL_WEBTUNNEL_CONN_LINE_5, + TorBridge::ADDITIONAL_WEBTUNNEL_CONN_LINE_6, + TorBridge::ADDITIONAL_WEBTUNNEL_CONN_LINE_7, + ]; + /// 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"; /// Default Snowflake protocol connection line. diff --git a/src/wallet/types.rs b/src/wallet/types.rs index 14b6760..264bf95 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -398,9 +398,12 @@ impl WalletTransaction { /// Check if possible to repeat transaction action. pub fn can_repeat_action(&self) -> bool { if let Some(a) = &self.action { - return self.action_error.is_some() && a != &WalletTransactionAction::Cancelling + self.action_error.is_some() && a != &WalletTransactionAction::Cancelling + } else { + // Can resend over Tor. + !self.data.confirmed && !self.sending_tor() && !self.broadcasting() && + self.receiver.is_some() } - false } /// Check if transaction is broadcasting after finalization. diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index c754f4a..e97525d 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -1596,11 +1596,10 @@ async fn handle_task(w: &Wallet, t: WalletTask) { } Err(e) => { error!("send tor finalize error: {:?}", e); - sync_wallet_data(&w, false); if let Some(tx) = w.retrieve_tx_by_id(s.id) { let _ = w.cancel(&tx); + sync_wallet_data(&w, false); } - w.on_tx_error(id, Some(e)); } } } @@ -1711,9 +1710,8 @@ async fn handle_task(w: &Wallet, t: WalletTask) { w.send_creating.store(true, Ordering::Relaxed); if let Ok(s) = w.send(*a, r.clone()) { sync_wallet_data(&w, false); - if r.is_some() && Tor::is_service_running(&w.identifier()) { + if let Some(addr) = r { w.send_creating.store(false, Ordering::Relaxed); - let addr = r.as_ref().unwrap(); send_tor(&s, addr).await; return; } else {