tor: bridge connection line, save changes on modal close

This commit is contained in:
ardocrat 2024-05-17 12:36:05 +03:00
parent 206c89520c
commit e379a7bf86
6 changed files with 142 additions and 112 deletions

View file

@ -134,6 +134,8 @@ transport:
tor_settings: Tor Settings tor_settings: Tor Settings
bridges: Bridges bridges: Bridges
bridges_desc: Setup bridges to bypass Tor network censorship if usual connection is not working. bridges_desc: Setup bridges to bypass Tor network censorship if usual connection is not working.
bin_file: 'Binary file:'
conn_line: 'Connection line:'
network: network:
self: Network self: Network
type: 'Network type:' type: 'Network type:'

View file

@ -134,6 +134,8 @@ transport:
tor_settings: Настройки Tor tor_settings: Настройки Tor
bridges: Мосты bridges: Мосты
bridges_desc: Настройте мосты для обхода цензуры сети Tor, если обычное соединение не работает. bridges_desc: Настройте мосты для обхода цензуры сети Tor, если обычное соединение не работает.
bin_file: 'Исполняемый файл:'
conn_line: 'Строка подключения:'
network: network:
self: Сеть self: Сеть
type: 'Тип сети:' type: 'Тип сети:'

View file

@ -54,8 +54,12 @@ pub struct WalletTransport {
/// QR code address image [`Modal`] content. /// QR code address image [`Modal`] content.
qr_code_content: QrCodeContent, qr_code_content: QrCodeContent,
/// Flag to check if Tor settings were changed.
tor_settings_changed: bool,
/// Tor bridge binary path edit text. /// Tor bridge binary path edit text.
bridge_bin_path_edit: String, bridge_bin_path_edit: String,
/// Tor bridge connection line edit text.
bridge_conn_line_edit: String,
} }
impl WalletTab for WalletTransport { impl WalletTab for WalletTransport {
@ -118,10 +122,10 @@ impl WalletTransport {
pub fn new(addr: String) -> Self { pub fn new(addr: String) -> Self {
// Setup Tor bridge binary path edit text. // Setup Tor bridge binary path edit text.
let bridge = TorConfig::get_bridge(); let bridge = TorConfig::get_bridge();
let bridge_bin_path_edit = if let Some(b) = bridge { let (bin_path, conn_line) = if let Some(b) = bridge {
b.binary_path() (b.binary_path(), b.connection_line())
} else { } else {
"".to_string() ("".to_string(), "".to_string())
}; };
Self { Self {
tor_sending: Arc::new(RwLock::new(false)), tor_sending: Arc::new(RwLock::new(false)),
@ -132,7 +136,9 @@ impl WalletTransport {
address_error: false, address_error: false,
modal_just_opened: false, modal_just_opened: false,
qr_code_content: QrCodeContent::new(addr), 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. /// 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. // Setup layout size.
let mut rect = ui.available_rect_before_wrap(); let mut rect = ui.available_rect_before_wrap();
rect.set_height(78.0); rect.set_height(78.0);
@ -212,10 +218,12 @@ impl WalletTransport {
// Draw button to setup Tor transport. // Draw button to setup Tor transport.
let button_rounding = View::item_rounding(0, 2, true); let button_rounding = View::item_rounding(0, 2, true);
View::item_button(ui, button_rounding, GEAR_SIX, None, || { View::item_button(ui, button_rounding, GEAR_SIX, None, || {
self.tor_settings_changed = false;
// Show Tor settings modal. // Show Tor settings modal.
Modal::new(TOR_SETTINGS_MODAL) Modal::new(TOR_SETTINGS_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)
.title(t!("transport.tor_settings")) .title(t!("transport.tor_settings"))
.closeable(false)
.show(); .show();
}); });
@ -285,19 +293,6 @@ impl WalletTransport {
let os = OperatingSystem::from_target_os(); let os = OperatingSystem::from_target_os();
let show_bridges = os != OperatingSystem::Android; 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); ui.add_space(6.0);
if show_bridges { if show_bridges {
let bridge = TorConfig::get_bridge(); let bridge = TorConfig::get_bridge();
@ -312,13 +307,13 @@ impl WalletTransport {
let value = if bridge.is_some() { let value = if bridge.is_some() {
None None
} else { } else {
let b = TorConfig::get_snowflake(); let default_bridge = TorConfig::get_obfs4();
self.bridge_bin_path_edit = b.binary_path(); self.bridge_bin_path_edit = default_bridge.binary_path();
Some(b) self.bridge_conn_line_edit = default_bridge.connection_line();
Some(default_bridge)
}; };
TorConfig::save_bridge(value); TorConfig::save_bridge(value);
// Restart running service or rebuild client. self.tor_settings_changed = true;
restart_or_rebuild();
}); });
}); });
@ -330,50 +325,79 @@ impl WalletTransport {
ui.add_space(6.0); ui.add_space(6.0);
ui.columns(2, |columns| { ui.columns(2, |columns| {
columns[0].vertical_centered(|ui| { 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. // Draw Obfs4 bridge selector.
let obfs4 = TorConfig::get_obfs4(); let obfs4 = TorConfig::get_obfs4();
let name = obfs4.protocol_name().to_uppercase(); let name = obfs4.protocol_name().to_uppercase();
View::radio_value(ui, &mut bridge, obfs4, name); 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); ui.add_space(12.0);
// Check if bridge type was changed to save. // Check if bridge type was changed to save.
if current_bridge != bridge { if current_bridge != bridge {
self.tor_settings_changed = true;
TorConfig::save_bridge(Some(bridge.clone())); TorConfig::save_bridge(Some(bridge.clone()));
self.bridge_bin_path_edit = bridge.binary_path(); self.bridge_bin_path_edit = bridge.binary_path();
// Restart running service or rebuild client. self.bridge_conn_line_edit = bridge.connection_line();
restart_or_rebuild();
} }
// Draw binary path text edit. // Draw binary path text edit.
let bin_edit_id = Id::from(modal.id).with(wallet.get_config().id).with("_bin_edit"); let bin_edit_id = Id::from(modal.id)
let bin_edit_opts = TextEditOptions::new(bin_edit_id).paste(); .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(); let bin_edit_before = self.bridge_bin_path_edit.clone();
ui.vertical_centered(|ui| { 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); 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. // Draw connection line text edit.
if bin_edit_before != self.bridge_bin_path_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 { let b = match bridge {
TorBridge::Snowflake(_) => { TorBridge::Snowflake(_, _) => {
TorBridge::Snowflake(self.bridge_bin_path_edit.clone()) TorBridge::Snowflake(bin_path, conn_line)
}, },
TorBridge::Obfs4(_) => { TorBridge::Obfs4(_, _) => {
TorBridge::Obfs4(self.bridge_bin_path_edit.clone()) TorBridge::Obfs4(bin_path, conn_line)
} }
}; };
TorConfig::save_bridge(Some(b)); TorConfig::save_bridge(Some(b));
// Restart running service or rebuild client. self.tor_settings_changed = true;
restart_or_rebuild();
} }
ui.add_space(2.0); ui.add_space(2.0);
} }
@ -396,6 +420,19 @@ impl WalletTransport {
ui.add_space(6.0); ui.add_space(6.0);
ui.vertical_centered_justified(|ui| { ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::WHITE, || { 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(); modal.close();
}); });
}); });

View file

@ -33,8 +33,14 @@ impl Default for TorConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
bridge: None, bridge: None,
obfs4: TorBridge::Obfs4(TorBridge::DEFAULT_OBFS4_BIN_PATH.to_string()), obfs4: TorBridge::Obfs4(
snowflake: TorBridge::Snowflake(TorBridge::DEFAULT_SNOWFLAKE_BIN_PATH.to_string()), 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() { if bridge.is_some() {
let bridge = bridge.unwrap(); let bridge = bridge.unwrap();
match &bridge { match &bridge {
TorBridge::Snowflake(_) => { TorBridge::Snowflake(_, _) => {
w_tor_config.snowflake = bridge w_tor_config.snowflake = bridge
} }
TorBridge::Obfs4(_) => { TorBridge::Obfs4(_, _) => {
w_tor_config.obfs4 = bridge w_tor_config.obfs4 = bridge
} }
} }

View file

@ -104,8 +104,12 @@ impl Tor {
let bridge = TorConfig::get_bridge(); let bridge = TorConfig::get_bridge();
if let Some(b) = bridge { if let Some(b) = bridge {
match b { match b {
super::TorBridge::Snowflake(path) => Self::build_snowflake(&mut builder, path), super::TorBridge::Snowflake(path, conn) => {
super::TorBridge::Obfs4(path) => Self::build_obfs4(&mut builder, path), Self::build_snowflake(&mut builder, path, conn)
},
super::TorBridge::Obfs4(path, conn) => {
Self::build_obfs4(&mut builder, path, conn)
},
} }
} }
// Setup address filter. // Setup address filter.
@ -184,6 +188,7 @@ impl Tor {
// Restart Onion service. // Restart Onion service.
pub fn restart_service(port: u16, key: SecretKey, id: &String) { pub fn restart_service(port: u16, key: SecretKey, id: &String) {
println!("restart service");
Self::stop_service(id); Self::stop_service(id);
Self::rebuild_client(); Self::rebuild_client();
Self::start_service(port, key, id) Self::start_service(port, key, id)
@ -283,8 +288,10 @@ impl Tor {
.body(Body::from(data)) .body(Body::from(data))
.unwrap(); .unwrap();
// Send request. // Send request.
println!("Send request");
let duration = match http.request(req).await { let duration = match http.request(req).await {
Ok(_) => { Ok(_) => {
println!("OK!");
// Remove service from starting. // Remove service from starting.
let mut w_services = TOR_SERVER_STATE.starting_services.write(); let mut w_services = TOR_SERVER_STATE.starting_services.write();
w_services.remove(&service_id); w_services.remove(&service_id);
@ -292,6 +299,7 @@ impl Tor {
Duration::from_millis(15000) Duration::from_millis(15000)
}, },
Err(e) => { Err(e) => {
println!("err: {}", e);
// Restart service on 3rd error. // Restart service on 3rd error.
errors_count += 1; errors_count += 1;
if errors_count == MAX_ERRORS { if errors_count == MAX_ERRORS {
@ -350,6 +358,8 @@ impl Tor {
let mut w_services = TOR_SERVER_STATE.running_services.write(); let mut w_services = TOR_SERVER_STATE.running_services.write();
w_services.insert(id.clone(), (service.clone(), proxy.clone())); w_services.insert(id.clone(), (service.clone(), proxy.clone()));
println!("run_service_proxy done");
// Start proxy for launched service. // Start proxy for launched service.
client client
.runtime() .runtime()
@ -417,77 +427,35 @@ impl Tor {
.unwrap(); .unwrap();
} }
fn build_snowflake(builder: &mut TorClientConfigBuilder, bin_path: String) { fn build_snowflake(builder: &mut TorClientConfigBuilder, bin_path: String, conn_line: String) {
// Add a single bridge to the list of bridges, from a bridge line. let bridge_line = format!("Bridge {}", conn_line);
// This line comes from https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/blob/main/projects/common/bridges_list.snowflake.txt if let Ok(bridge) = bridge_line.parse() {
// this is a real bridge line you can use as-is, after making sure it's still up to date with builder.bridges().bridges().push(bridge);
// 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);
// Now configure an snowflake transport. (Requires the "pt-client" feature) // Now configure an snowflake transport. (Requires the "pt-client" feature)
let mut transport = TransportConfigBuilder::default(); let mut transport = TransportConfigBuilder::default();
transport transport
.protocols(vec!["snowflake".parse().unwrap()]) .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())) .path(CfgPath::new(bin_path.into()))
.run_on_startup(true); .run_on_startup(true);
builder.bridges().set_transports(vec![transport]); builder.bridges().set_transports(vec![transport]);
} }
fn build_obfs4(builder: &mut TorClientConfigBuilder, bin_path: String) { fn build_obfs4(builder: &mut TorClientConfigBuilder, bin_path: String, conn_line: String) {
// This bridge line is made up for demonstration, and won't work. let bridge_line = format!("Bridge {}", conn_line);
const BRIDGE1_LINE : &str = "Bridge obfs4 82.64.231.149:9300 1B4382D598392D72F85F8D3D05CE1A6BB0EF0300 cert=+NYVc7iy+d6lj/NZZY2ZPFR6IHA/Mw5XSwFxvPFSespE+A/e5BOv2kgGbJbDzkkRfCsGGg iat-mode=0"; if let Ok(bridge) = bridge_line.parse() {
let bridge_1: BridgeConfigBuilder = BRIDGE1_LINE.parse().unwrap(); builder.bridges().bridges().push(bridge);
// 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);
// Now configure an obfs4 transport. (Requires the "pt-client" feature) // Now configure an obfs4 transport. (Requires the "pt-client" feature)
let mut transport = TransportConfigBuilder::default(); let mut transport = TransportConfigBuilder::default();
transport transport
.protocols(vec!["obfs4".parse().unwrap()]) .protocols(vec!["obfs4".parse().unwrap()])
// Specify either the name or the absolute path of pluggable transport client binary, this // Specify either the name or the absolute path of pluggable transport client binary,
// may differ from system to system. // this may differ from system to system.
.path(CfgPath::new(bin_path.into())) .path(CfgPath::new(bin_path.into()))
.run_on_startup(true); .run_on_startup(true);
builder.bridges().transports().push(transport); builder.bridges().transports().push(transport);

View file

@ -14,32 +14,47 @@
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
/// Tor network bridge type with binary path. /// Tor network bridge type.
#[derive(Serialize, Deserialize, Clone, PartialEq)] #[derive(Serialize, Deserialize, Clone, PartialEq)]
pub enum TorBridge { pub enum TorBridge {
Snowflake(String), /// Obfs4 bridge with connection line and binary path.
Obfs4(String) Obfs4(String, String),
/// Snowflake bridge with connection line and binary path.
Snowflake(String, String)
} }
impl TorBridge { 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. /// Default Obfs4 protocol proxy client binary path.
pub const DEFAULT_OBFS4_BIN_PATH: &'static str = "/usr/bin/obfs4proxy"; 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. /// Get bridge protocol name.
pub fn protocol_name(&self) -> String { pub fn protocol_name(&self) -> String {
match *self { 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. /// Get bridge client binary path.
pub fn binary_path(&self) -> String { pub fn binary_path(&self) -> String {
match self { 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()
} }
} }
} }