tor: multiline bridges input, optimize tor connection check, add multiple default webtunnel bridges, fix tx cancel on finalization error

This commit is contained in:
ardocrat
2026-03-18 15:44:32 +03:00
parent a0947aa47c
commit ba0af0968d
9 changed files with 383 additions and 189 deletions
+56 -12
View File
@@ -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| {
@@ -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()), || {
@@ -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();
});
+7 -6
View File
@@ -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()));
}
}
});
}
+2 -1
View File
@@ -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())
)
}
+289 -161
View File
@@ -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<Tor> = Arc::new(Tor::default());
/// Static thread-aware state of Tor to be updated from separate thread.
static ref TOR_STATE: Arc<Tor> = 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<RwLock<(TorClient<TokioNativeTlsRuntime>, TorClientConfig)>>,
/// Client to check services availability.
check_client: Arc<RwLock<
Option<hyper_tor::Client<ArtiHttpConnector<TokioNativeTlsRuntime, TlsConnector>>>
>>,
client_config: Arc<RwLock<Option<(TorClient<TokioNativeTlsRuntime>, TorClientConfig)>>>,
/// Flag to check if client is launching.
client_launching: Arc<AtomicBool>,
/// Mapping of running Onion services identifiers to proxy.
run: Arc<RwLock<
BTreeMap<String, (u16, SecretKey, Arc<RunningOnionService>, Arc<OnionServiceReverseProxy>)>
>>,
/// Starting Onion services identifiers.
start: Arc<RwLock<BTreeSet<String>>>,
/// Mapping of starting Onion services identifiers.
start: Arc<RwLock<
BTreeMap<String, (u16, SecretKey)>
>>,
/// Failed Onion services identifiers.
fail: Arc<RwLock<BTreeSet<String>>>,
/// 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<Vec<TorBridge>>) -> 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<TorClient<TokioNativeTlsRuntime>> {
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::<Vec<String>>(&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::<Vec<_>>();
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<String> {
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<TokioNativeTlsRuntime>, TorClientConfig) {
let r_client_config = TOR_SERVER_STATE.client_config.read();
fn client_config() -> Option<(TorClient<TokioNativeTlsRuntime>, 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::<Vec<String>>()
};
let mut services: BTreeMap<String, (u16, SecretKey)> = BTreeMap::new();
let mut services: BTreeMap<String, (u16, SecretKey)> = 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<S>(
client: TorClient<TokioNativeTlsRuntime>,
addr: SocketAddr,
request: S,
nickname: HsNickname,
@@ -500,7 +629,6 @@ impl Tor {
S: futures::Stream<Item = tor_hsservice::RendRequest> + 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);
}
}
+19
View File
@@ -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.
+5 -2
View File
@@ -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.
+2 -4
View File
@@ -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 {