mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-19 13:19:39 +00:00
tor: multiline bridges input, optimize tor connection check, add multiple default webtunnel bridges, fix tx cancel on finalization error
This commit is contained in:
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user