diff --git a/locales/en.yml b/locales/en.yml index d9d6985..8cf13ef 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -134,6 +134,8 @@ transport: tor_settings: Tor Settings bridges: Bridges bridges_desc: Setup bridges to bypass Tor network censorship if usual connection is not working. + bin_file: 'Binary file:' + conn_line: 'Connection line:' network: self: Network type: 'Network type:' diff --git a/locales/ru.yml b/locales/ru.yml index e716342..2c9be8c 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -134,6 +134,8 @@ transport: tor_settings: Настройки Tor bridges: Мосты bridges_desc: Настройте мосты для обхода цензуры сети Tor, если обычное соединение не работает. + bin_file: 'Исполняемый файл:' + conn_line: 'Строка подключения:' network: self: Сеть type: 'Тип сети:' diff --git a/src/gui/views/wallets/wallet/transport.rs b/src/gui/views/wallets/wallet/transport.rs index ba8cde0..822c2f6 100644 --- a/src/gui/views/wallets/wallet/transport.rs +++ b/src/gui/views/wallets/wallet/transport.rs @@ -54,8 +54,12 @@ pub struct WalletTransport { /// QR code address image [`Modal`] content. qr_code_content: QrCodeContent, + /// Flag to check if Tor settings were changed. + tor_settings_changed: bool, /// Tor bridge binary path edit text. bridge_bin_path_edit: String, + /// Tor bridge connection line edit text. + bridge_conn_line_edit: String, } impl WalletTab for WalletTransport { @@ -118,10 +122,10 @@ impl WalletTransport { pub fn new(addr: String) -> Self { // Setup Tor bridge binary path edit text. let bridge = TorConfig::get_bridge(); - let bridge_bin_path_edit = if let Some(b) = bridge { - b.binary_path() + let (bin_path, conn_line) = if let Some(b) = bridge { + (b.binary_path(), b.connection_line()) } else { - "".to_string() + ("".to_string(), "".to_string()) }; Self { tor_sending: Arc::new(RwLock::new(false)), @@ -132,7 +136,9 @@ impl WalletTransport { address_error: false, modal_just_opened: false, qr_code_content: QrCodeContent::new(addr), - bridge_bin_path_edit + tor_settings_changed: false, + bridge_bin_path_edit: bin_path, + bridge_conn_line_edit: conn_line, } } @@ -197,7 +203,7 @@ impl WalletTransport { } /// Draw Tor transport header content. - fn tor_header_ui(&self, ui: &mut egui::Ui, wallet: &Wallet) { + fn tor_header_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) { // Setup layout size. let mut rect = ui.available_rect_before_wrap(); rect.set_height(78.0); @@ -212,10 +218,12 @@ impl WalletTransport { // Draw button to setup Tor transport. let button_rounding = View::item_rounding(0, 2, true); View::item_button(ui, button_rounding, GEAR_SIX, None, || { + self.tor_settings_changed = false; // Show Tor settings modal. Modal::new(TOR_SETTINGS_MODAL) .position(ModalPosition::CenterTop) .title(t!("transport.tor_settings")) + .closeable(false) .show(); }); @@ -285,19 +293,6 @@ impl WalletTransport { let os = OperatingSystem::from_target_os(); let show_bridges = os != OperatingSystem::Android; - // Restart running service or rebuild client. - let restart_or_rebuild = || { - let service_id = &wallet.identifier(); - if Tor::is_service_running(service_id) { - if let Ok(key) = wallet.secret_key() { - let api_port = wallet.foreign_api_port().unwrap(); - Tor::restart_service(api_port, key, service_id); - } - } else { - Tor::rebuild_client(); - } - }; - ui.add_space(6.0); if show_bridges { let bridge = TorConfig::get_bridge(); @@ -312,13 +307,13 @@ impl WalletTransport { let value = if bridge.is_some() { None } else { - let b = TorConfig::get_snowflake(); - self.bridge_bin_path_edit = b.binary_path(); - Some(b) + let default_bridge = TorConfig::get_obfs4(); + self.bridge_bin_path_edit = default_bridge.binary_path(); + self.bridge_conn_line_edit = default_bridge.connection_line(); + Some(default_bridge) }; TorConfig::save_bridge(value); - // Restart running service or rebuild client. - restart_or_rebuild(); + self.tor_settings_changed = true; }); }); @@ -330,50 +325,79 @@ impl WalletTransport { ui.add_space(6.0); ui.columns(2, |columns| { columns[0].vertical_centered(|ui| { - // Draw Snowflake bridge selector. - let snowflake = TorConfig::get_snowflake(); - let name = snowflake.protocol_name().to_uppercase(); - View::radio_value(ui, &mut bridge, snowflake, name); - }); - columns[1].vertical_centered(|ui| { // Draw Obfs4 bridge selector. let obfs4 = TorConfig::get_obfs4(); let name = obfs4.protocol_name().to_uppercase(); View::radio_value(ui, &mut bridge, obfs4, name); }); + columns[1].vertical_centered(|ui| { + // Draw Snowflake bridge selector. + let snowflake = TorConfig::get_snowflake(); + let name = snowflake.protocol_name().to_uppercase(); + View::radio_value(ui, &mut bridge, snowflake, name); + }); }); ui.add_space(12.0); // Check if bridge type was changed to save. if current_bridge != bridge { + self.tor_settings_changed = true; TorConfig::save_bridge(Some(bridge.clone())); self.bridge_bin_path_edit = bridge.binary_path(); - // Restart running service or rebuild client. - restart_or_rebuild(); + self.bridge_conn_line_edit = bridge.connection_line(); } // Draw binary path text edit. - let bin_edit_id = Id::from(modal.id).with(wallet.get_config().id).with("_bin_edit"); - let bin_edit_opts = TextEditOptions::new(bin_edit_id).paste(); + let bin_edit_id = Id::from(modal.id) + .with(wallet.get_config().id) + .with("_bin_edit"); + let bin_edit_opts = TextEditOptions::new(bin_edit_id) + .paste() + .no_focus(); let bin_edit_before = self.bridge_bin_path_edit.clone(); ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("transport.bin_file")) + .size(17.0) + .color(Colors::INACTIVE_TEXT)); + ui.add_space(6.0); View::text_edit(ui, cb, &mut self.bridge_bin_path_edit, bin_edit_opts); + ui.add_space(6.0); }); - // Check if text was changed to save bin path. - if bin_edit_before != self.bridge_bin_path_edit { + // Draw connection line text edit. + let conn_edit_id = Id::from(modal.id) + .with(wallet.get_config().id) + .with("_conn_edit"); + let conn_edit_opts = TextEditOptions::new(conn_edit_id) + .paste() + .scan_qr() + .no_focus(); + let conn_edit_before = self.bridge_conn_line_edit.clone(); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("transport.conn_line")) + .size(17.0) + .color(Colors::INACTIVE_TEXT)); + ui.add_space(6.0); + View::text_edit(ui, cb, &mut self.bridge_conn_line_edit, conn_edit_opts); + }); + + // Check if bin path or connection line text was changed to save bridge. + if conn_edit_before != self.bridge_conn_line_edit || + bin_edit_before != self.bridge_bin_path_edit { + let bin_path = self.bridge_bin_path_edit.clone(); + let conn_line = self.bridge_conn_line_edit.clone(); let b = match bridge { - TorBridge::Snowflake(_) => { - TorBridge::Snowflake(self.bridge_bin_path_edit.clone()) + TorBridge::Snowflake(_, _) => { + TorBridge::Snowflake(bin_path, conn_line) }, - TorBridge::Obfs4(_) => { - TorBridge::Obfs4(self.bridge_bin_path_edit.clone()) + TorBridge::Obfs4(_, _) => { + TorBridge::Obfs4(bin_path, conn_line) } }; TorConfig::save_bridge(Some(b)); - // Restart running service or rebuild client. - restart_or_rebuild(); + self.tor_settings_changed = true; } + ui.add_space(2.0); } @@ -396,6 +420,19 @@ impl WalletTransport { ui.add_space(6.0); ui.vertical_centered_justified(|ui| { View::button(ui, t!("close"), Colors::WHITE, || { + if self.tor_settings_changed { + self.tor_settings_changed = false; + // Restart running service or rebuild client. + let service_id = &wallet.identifier(); + if Tor::is_service_running(service_id) { + if let Ok(key) = wallet.secret_key() { + let api_port = wallet.foreign_api_port().unwrap(); + Tor::restart_service(api_port, key, service_id); + } + } else { + Tor::rebuild_client(); + } + } modal.close(); }); }); diff --git a/src/tor/config.rs b/src/tor/config.rs index f1796a4..ab1620b 100644 --- a/src/tor/config.rs +++ b/src/tor/config.rs @@ -33,8 +33,14 @@ impl Default for TorConfig { fn default() -> Self { Self { bridge: None, - obfs4: TorBridge::Obfs4(TorBridge::DEFAULT_OBFS4_BIN_PATH.to_string()), - snowflake: TorBridge::Snowflake(TorBridge::DEFAULT_SNOWFLAKE_BIN_PATH.to_string()), + obfs4: TorBridge::Obfs4( + TorBridge::DEFAULT_OBFS4_BIN_PATH.to_string(), + TorBridge::DEFAULT_OBFS4_CONN_LINE.to_string() + ), + snowflake: TorBridge::Snowflake( + TorBridge::DEFAULT_SNOWFLAKE_BIN_PATH.to_string(), + TorBridge::DEFAULT_SNOWFLAKE_CONN_LINE.to_string() + ), } } } @@ -89,10 +95,10 @@ impl TorConfig { if bridge.is_some() { let bridge = bridge.unwrap(); match &bridge { - TorBridge::Snowflake(_) => { + TorBridge::Snowflake(_, _) => { w_tor_config.snowflake = bridge } - TorBridge::Obfs4(_) => { + TorBridge::Obfs4(_, _) => { w_tor_config.obfs4 = bridge } } diff --git a/src/tor/tor.rs b/src/tor/tor.rs index b79d628..493de87 100644 --- a/src/tor/tor.rs +++ b/src/tor/tor.rs @@ -104,8 +104,12 @@ impl Tor { let bridge = TorConfig::get_bridge(); if let Some(b) = bridge { match b { - super::TorBridge::Snowflake(path) => Self::build_snowflake(&mut builder, path), - super::TorBridge::Obfs4(path) => Self::build_obfs4(&mut builder, path), + super::TorBridge::Snowflake(path, conn) => { + Self::build_snowflake(&mut builder, path, conn) + }, + super::TorBridge::Obfs4(path, conn) => { + Self::build_obfs4(&mut builder, path, conn) + }, } } // Setup address filter. @@ -184,6 +188,7 @@ impl Tor { // Restart Onion service. pub fn restart_service(port: u16, key: SecretKey, id: &String) { + println!("restart service"); Self::stop_service(id); Self::rebuild_client(); Self::start_service(port, key, id) @@ -283,8 +288,10 @@ impl Tor { .body(Body::from(data)) .unwrap(); // Send request. + println!("Send request"); let duration = match http.request(req).await { Ok(_) => { + println!("OK!"); // Remove service from starting. let mut w_services = TOR_SERVER_STATE.starting_services.write(); w_services.remove(&service_id); @@ -292,6 +299,7 @@ impl Tor { Duration::from_millis(15000) }, Err(e) => { + println!("err: {}", e); // Restart service on 3rd error. errors_count += 1; if errors_count == MAX_ERRORS { @@ -350,6 +358,8 @@ impl Tor { let mut w_services = TOR_SERVER_STATE.running_services.write(); w_services.insert(id.clone(), (service.clone(), proxy.clone())); + println!("run_service_proxy done"); + // Start proxy for launched service. client .runtime() @@ -417,77 +427,35 @@ impl Tor { .unwrap(); } - fn build_snowflake(builder: &mut TorClientConfigBuilder, bin_path: String) { - // Add a single bridge to the list of bridges, from a bridge line. - // This line comes from https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/blob/main/projects/common/bridges_list.snowflake.txt - // this is a real bridge line you can use as-is, after making sure it's still up to date with - // above link. - const BRIDGE1_LINE : &str = "Bridge snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn"; - let bridge_1: BridgeConfigBuilder = BRIDGE1_LINE.parse().unwrap(); - builder.bridges().bridges().push(bridge_1); - - // Add a second bridge, built by hand. We use the 2nd bridge line from above, but modify some - // parameters to use AMP Cache instead of Fastly as a signaling channel. The difference in - // configuration is detailed in - // https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/tree/main/client#amp-cache - // let mut bridge2_builder = BridgeConfigBuilder::default(); - // bridge2_builder - // .transport("snowflake") - // .push_setting( - // "fingerprint", - // "8838024498816A039FCBBAB14E6F40A0843051FA" - // ) - // .push_setting("url", "https://snowflake-broker.torproject.net/") - // .push_setting("ampcache", "https://cdn.ampproject.org/") - // .push_setting("front", "www.google.com") - // .push_setting( - // "ice", - // "stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478", - // ) - // .push_setting("utls-imitate", "hellorandomizedalpn"); - // bridge2_builder.set_addrs(vec!["192.0.2.4:80".parse().unwrap()]); - // bridge2_builder.set_ids(vec!["8838024498816A039FCBBAB14E6F40A0843051FA".parse().unwrap()]); - // // Now insert the second bridge into our config builder. - // builder.bridges().bridges().push(bridge2_builder); + fn build_snowflake(builder: &mut TorClientConfigBuilder, bin_path: String, conn_line: String) { + let bridge_line = format!("Bridge {}", conn_line); + if let Ok(bridge) = bridge_line.parse() { + builder.bridges().bridges().push(bridge); + } // Now configure an snowflake transport. (Requires the "pt-client" feature) let mut transport = TransportConfigBuilder::default(); transport .protocols(vec!["snowflake".parse().unwrap()]) - // this might be named differently on some systems, this should work on Debian, but Archlinux is known to use `snowflake-pt-client` instead for instance. + // this might be named differently on some systems, this should work on Debian, + // but Archlinux is known to use `snowflake-pt-client` instead for instance. .path(CfgPath::new(bin_path.into())) .run_on_startup(true); builder.bridges().set_transports(vec![transport]); } - fn build_obfs4(builder: &mut TorClientConfigBuilder, bin_path: String) { - // This bridge line is made up for demonstration, and won't work. - const BRIDGE1_LINE : &str = "Bridge obfs4 82.64.231.149:9300 1B4382D598392D72F85F8D3D05CE1A6BB0EF0300 cert=+NYVc7iy+d6lj/NZZY2ZPFR6IHA/Mw5XSwFxvPFSespE+A/e5BOv2kgGbJbDzkkRfCsGGg iat-mode=0"; - let bridge_1: BridgeConfigBuilder = BRIDGE1_LINE.parse().unwrap(); - // This is where we pass `BRIDGE1_LINE` into the BridgeConfigBuilder. - builder.bridges().bridges().push(bridge_1); - - // Add a second bridge, built by hand. This way is harder. - // This bridge is made up for demonstration, and won't work. - // let mut bridge2_builder = BridgeConfigBuilder::default(); - // bridge2_builder - // .transport("obfs4") - // .push_setting("iat-mode", "1") - // .push_setting( - // "cert", - // "YnV0IHNvbWV0aW1lcyB0aGV5IGFyZSByYW5kb20u8x9aQG/0cIIcx0ItBcTqiSXotQne+Q" - // ); - // bridge2_builder.set_addrs(vec!["198.51.100.25:443".parse().unwrap()]); - // bridge2_builder.set_ids(vec!["7DD62766BF2052432051D7B7E08A22F7E34A4543".parse().unwrap()]); - // // Now insert the second bridge into our config builder. - // builder.bridges().bridges().push(bridge2_builder); + fn build_obfs4(builder: &mut TorClientConfigBuilder, bin_path: String, conn_line: String) { + let bridge_line = format!("Bridge {}", conn_line); + if let Ok(bridge) = bridge_line.parse() { + builder.bridges().bridges().push(bridge); + } // Now configure an obfs4 transport. (Requires the "pt-client" feature) let mut transport = TransportConfigBuilder::default(); transport .protocols(vec!["obfs4".parse().unwrap()]) - // Specify either the name or the absolute path of pluggable transport client binary, this - // may differ from system to system. + // Specify either the name or the absolute path of pluggable transport client binary, + // this may differ from system to system. .path(CfgPath::new(bin_path.into())) .run_on_startup(true); builder.bridges().transports().push(transport); diff --git a/src/tor/types.rs b/src/tor/types.rs index 7ccaa60..4a4b0ed 100644 --- a/src/tor/types.rs +++ b/src/tor/types.rs @@ -14,32 +14,47 @@ use serde_derive::{Deserialize, Serialize}; -/// Tor network bridge type with binary path. +/// Tor network bridge type. #[derive(Serialize, Deserialize, Clone, PartialEq)] pub enum TorBridge { - Snowflake(String), - Obfs4(String) + /// Obfs4 bridge with connection line and binary path. + Obfs4(String, String), + /// Snowflake bridge with connection line and binary path. + Snowflake(String, String) } impl TorBridge { - /// Default Snowflake protocol client binary path. - pub const DEFAULT_SNOWFLAKE_BIN_PATH: &'static str = "/usr/bin/snowflake-client"; /// Default Obfs4 protocol proxy client binary path. pub const DEFAULT_OBFS4_BIN_PATH: &'static str = "/usr/bin/obfs4proxy"; + /// Default Snowflake protocol client binary path. + pub const DEFAULT_SNOWFLAKE_BIN_PATH: &'static str = "/usr/bin/snowflake-client"; + + /// Default Obfs4 protocol connection line. + pub const DEFAULT_OBFS4_CONN_LINE: &'static str = "obfs4 obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0"; + /// Default Snowflake protocol connection line. + pub const DEFAULT_SNOWFLAKE_CONN_LINE: &'static str = "snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn"; /// Get bridge protocol name. pub fn protocol_name(&self) -> String { match *self { - TorBridge::Snowflake(_) => "snowflake".to_string(), - TorBridge::Obfs4(_) => "obfs4".to_string() + TorBridge::Obfs4(_, _) => "obfs4".to_string(), + TorBridge::Snowflake(_, _) => "snowflake".to_string() } } /// Get bridge client binary path. pub fn binary_path(&self) -> String { match self { - TorBridge::Snowflake(path) => path.clone(), - TorBridge::Obfs4(path) => path.clone() + TorBridge::Obfs4(path, _) => path.clone(), + TorBridge::Snowflake(path, _) => path.clone() + } + } + + /// Get bridge client connection line. + pub fn connection_line(&self) -> String { + match self { + TorBridge::Obfs4(_, line) => line.clone(), + TorBridge::Snowflake(_, line) => line.clone() } } } \ No newline at end of file