diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml
index e93994bc..526c2932 100644
--- a/.forgejo/workflows/release.yaml
+++ b/.forgejo/workflows/release.yaml
@@ -367,7 +367,7 @@ jobs:
- name: Release Windows x86
run: |
cargo build --release --target x86_64-pc-windows-gnu
- zip grim-${{ needs.version.outputs.v }}-win-x86_64.zip target/x86_64-pc-windows-gnu/release/grim.exe
+ zip -j grim-${{ needs.version.outputs.v }}-win-x86_64.zip target/x86_64-pc-windows-gnu/release/grim.exe
mv grim-${{ needs.version.outputs.v }}-win-x86_64.zip release/
- name: Save cargo cache
uses: actions/cache/save@v5
@@ -389,7 +389,7 @@ jobs:
release:
if: ${{ forgejo.ref_type == 'branch' || needs.version.outputs.exists == 'false' }}
- runs-on: ubuntu
+ runs-on: debian-release
needs: [version, android_release, linux, linux_x86, macos, windows]
steps:
- name: Download All Artifacts
diff --git a/.gitmodules b/.gitmodules
index d7703fc8..544acfd5 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,4 +4,8 @@
[submodule "wallet"]
path = wallet
url = https://code.gri.mw/ardocrat/wallet
- branch = grim
\ No newline at end of file
+ branch = grim
+[submodule "tor/webtunnel"]
+ path = tor/webtunnel
+ url = https://code.gri.mw/WEB/webtunnel
+ branch = grim
diff --git a/Cargo.toml b/Cargo.toml
index d95b022c..ba6c2de1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ license = "Apache-2.0"
repository = "https://code.gri.mw/GUI/grim"
keywords = [ "crypto", "grin", "mimblewimble" ]
edition = "2021"
+build = "build.rs"
[[bin]]
name = "grim"
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 8283b54c..76bea15d 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -22,6 +22,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Main"
android:enableOnBackInvokedCallback="false"
+ android:extractNativeLibs="true"
tools:ignore="UnusedAttribute">
diff --git a/android/app/src/main/java/mw/gri/android/MainActivity.java b/android/app/src/main/java/mw/gri/android/MainActivity.java
index c85f2a41..777e98ce 100644
--- a/android/app/src/main/java/mw/gri/android/MainActivity.java
+++ b/android/app/src/main/java/mw/gri/android/MainActivity.java
@@ -90,6 +90,7 @@ public class MainActivity extends GameActivity {
Os.setenv("HOME", Objects.requireNonNull(getExternalFilesDir("")).getPath(), true);
Os.setenv("XDG_CACHE_HOME", Objects.requireNonNull(getExternalCacheDir()).getPath(), true);
Os.setenv("ARTI_FS_DISABLE_PERMISSION_CHECKS", "true", true);
+ Os.setenv("NATIVE_LIBS_DIR", getApplicationInfo().nativeLibraryDir, true);
} catch (ErrnoException e) {
throw new RuntimeException(e);
}
diff --git a/build.rs b/build.rs
new file mode 100644
index 00000000..8ae87bbc
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,65 @@
+use std::process::Command;
+use std::{env, fs};
+
+fn main() {
+ let out_dir = env::var("OUT_DIR").unwrap();
+ let tor_out_dir = format!("{}/tor", out_dir);
+ let mut webtunnel_file = format!("{}/webtunnel", tor_out_dir);
+ let exists = fs::exists(&webtunnel_file).unwrap();
+ if !exists {
+ // Create empty webtunnel file to allow build with include_bytes! macro.
+ fs::create_dir(&tor_out_dir).unwrap_or_default();
+ fs::File::create(&webtunnel_file).unwrap();
+ }
+
+ let target = env::var("TARGET").unwrap();
+ let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
+
+ let is_android = target_os == "android";
+ if is_android {
+ // Set a path to Android Webtunnel binary.
+ let arch = if target.contains("aarch64") {
+ "arm64-v8a"
+ } else if target.contains("arm") {
+ "armeabi-v7a"
+ } else {
+ "x86_64"
+ };
+ let root = env::var("CARGO_MANIFEST_DIR").unwrap();
+ webtunnel_file = format!("{}/android/app/src/main/jniLibs/{}/libwebtunnel.so", root, arch);
+ }
+
+ // Build if Webtunnel binary is empty or not exists.
+ let empty = match fs::File::open(&webtunnel_file) {
+ Ok(file) => file.metadata().unwrap().len() != 0,
+ Err(_) => true
+ };
+ let build = !exists || empty;
+ if build {
+ // Setup GOOS env variable.
+ let go_os = if target_os == "macos" {
+ "darwin"
+ } else {
+ target_os.as_str()
+ };
+ // Setup GOARCH env variable.
+ let go_arch = if target.contains("aarch64") {
+ "arm64"
+ } else if target.contains("arm") {
+ "arm"
+ } else {
+ "amd64"
+ };
+ // Run Webtunnel Go build.
+ if let Ok(out) = Command::new("bash")
+ .arg("./scripts/webtunnel.sh")
+ .arg(go_os)
+ .arg(go_arch)
+ .arg(webtunnel_file)
+ .output() {
+ if out.status.code().is_none() || out.status.code().unwrap() != 0 {
+ panic!("webtunnel go build failed:\n{:?}", out);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/webtunnel.sh b/scripts/webtunnel.sh
new file mode 100755
index 00000000..c163b3c1
--- /dev/null
+++ b/scripts/webtunnel.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+cd "$(dirname "$0")"
+
+# Skip if Go not found.
+if ! command -v go >/dev/null 2>&1
+then
+ echo "Go could not be found"
+ exit 0
+fi
+
+go_os=$1
+go_arch=$2
+
+echo "Go build for os: $go_os, arch: $go_arch"
+
+# Setup vars for Android.
+if [[ "$go_os" == "android" ]]; then
+ # Setup NDK root path env.
+ if [[ -z "$ANDROID_NDK_HOME" ]]; then
+ NDK_VERSION=$(cat ../android/app/build.gradle | grep 'ndkVersion' | cut -d \' -f 2)
+ ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION
+ fi
+ # Setup NDK host path.
+ if [[ "$(uname)" == "Darwin" ]]; then
+ arch_host=darwin-x86_64
+ else
+ if [[ "$(uname -m)" == "aarch64" ]]; then
+ arch_host=linux-arm64
+ else
+ arch_host=linux-x86_64
+ fi
+ fi
+ # Setup NDK target arch.
+ if [[ "$go_arch" == "arm64" ]]; then
+ arch_bin_prefix=aarch64-linux-android
+ elif [[ "$go_arch" == "arm" ]]; then
+ arch_bin_prefix=armv7a-linux-androideabi
+ else
+ arch_bin_prefix=x86_64-linux-android
+ fi
+
+ # Build for current target.
+ CGO_ENABLED=1 GOOS=$1 GOARCH=$2 CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${arch_host}/bin/${arch_bin_prefix}35-clang" CXX="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/${arch_path}/bin/${arch_bin_prefix}35-clang++" go build -C "../tor/webtunnel" -ldflags="-s -w" -o "$3" code.gri.mw/WEB/webtunnel/main/client
+else
+ if [[ "$go_os" == "windows" ]]; then
+ extra_flag="-H=windowsgui"
+ fi
+ GOOS=$1 GOARCH=$2 go build -C "../tor/webtunnel" -ldflags="-s -w ${extra_flag}" -o "$3" code.gri.mw/WEB/webtunnel/main/client
+fi
+
diff --git a/src/gui/views/settings/tor.rs b/src/gui/views/settings/tor.rs
index 853d4ec7..d736378e 100644
--- a/src/gui/views/settings/tor.rs
+++ b/src/gui/views/settings/tor.rs
@@ -13,6 +13,7 @@
// limitations under the License.
use egui::{Align, Id, Layout, RichText, StrokeKind};
+use egui::os::OperatingSystem;
use url::Url;
use crate::gui::icons::{CLOUD_CHECK, NOTCHES, PENCIL, SCAN, TERMINAL};
@@ -52,6 +53,29 @@ const BRIDGE_CONN_LINE_EDIT_MODAL: &'static str = "bridge_conn_line_edit_modal";
/// Identifier for [`Modal`] to scan bridge line from QR code.
const SCAN_BRIDGE_CONN_LINE_MODAL: &'static str = "scan_bridge_conn_line_modal";
+impl Default for TorSettingsContent {
+ fn default() -> Self {
+ // Setup Tor bridge binary path edit text.
+ let bridge = TorConfig::get_bridge();
+ let (bin_path, conn_line) = if let Some(b) = bridge {
+ (b.binary_path(), b.connection_line())
+ } else {
+ ("".to_string(), "".to_string())
+ };
+ Self {
+ settings_changed: false,
+ proxy_url_edit: "".to_string(),
+ proxy_url_error: false,
+ bridge_bin_path_edit: bin_path,
+ bridge_bin_pick_file: FilePickContent::new(
+ FilePickContentType::ItemButton(View::item_rounding(0, 1, true))
+ ).no_parse(),
+ bridge_conn_line_edit: conn_line,
+ bridge_qr_scan_content: None,
+ }
+ }
+}
+
impl ContentContainer for TorSettingsContent {
fn modal_ids(&self) -> Vec<&'static str> {
vec![
@@ -71,11 +95,13 @@ impl ContentContainer for TorSettingsContent {
if let Some(content) = self.bridge_qr_scan_content.as_mut() {
let mut close = false;
content.modal_ui(ui, cb, |res| {
- let line = res.text();
// Save connection line after scanning.
+ let line = res.text();
let bridge = TorConfig::get_bridge().unwrap();
- TorBridge::save_bridge_conn_line(&bridge, line);
- self.settings_changed = true;
+ if bridge.connection_line() != line {
+ TorBridge::save_bridge_conn_line(&bridge, line);
+ self.settings_changed = true;
+ }
close = true;
});
if close {
@@ -155,7 +181,7 @@ impl ContentContainer for TorSettingsContent {
let value = if bridge.is_some() {
None
} else {
- let default_bridge = TorConfig::get_obfs4();
+ let default_bridge = TorConfig::get_webtunnel();
self.bridge_bin_path_edit = default_bridge.binary_path();
self.bridge_conn_line_edit = default_bridge.connection_line();
Some(default_bridge)
@@ -165,42 +191,53 @@ impl ContentContainer for TorSettingsContent {
});
});
- // Draw bridges selection and path.
if bridge.is_some() {
- let current_bridge = bridge.unwrap();
- let mut bridge = current_bridge.clone();
-
ui.add_space(6.0);
- ui.columns(2, |columns| {
- columns[0].vertical_centered(|ui| {
- // Show Obfs4 bridge selector.
- let obfs4 = TorConfig::get_obfs4();
- let name = obfs4.protocol_name().to_uppercase();
- View::radio_value(ui, &mut bridge, obfs4, name);
+ // Show bridge selection for non-Android.
+ let is_android = OperatingSystem::from_target_os() == OperatingSystem::Android;
+ if !is_android {
+ let current_bridge = bridge.unwrap();
+ let mut bridge = current_bridge.clone();
+
+ ui.columns(2, |columns| {
+ columns[0].vertical_centered(|ui| {
+ // Show Webtunnel bridge selector.
+ let webtunnel = TorConfig::get_webtunnel();
+ let name = webtunnel.protocol_name().to_uppercase();
+ View::radio_value(ui, &mut bridge, webtunnel, name);
+
+ });
+ columns[1].vertical_centered(|ui| {
+ // Show 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| {
+ ui.add_space(10.0);
+ ui.vertical_centered(|ui| {
// Show 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(14.0);
+ ui.add_space(16.0);
- // Check if bridge type was changed to save.
- if current_bridge != bridge {
- TorConfig::save_bridge(Some(bridge.clone()));
- self.bridge_bin_path_edit = bridge.binary_path();
- self.bridge_conn_line_edit = bridge.connection_line();
- self.settings_changed = true;
+ // Check if bridge type was changed to save.
+ if current_bridge != bridge {
+ TorConfig::save_bridge(Some(bridge.clone()));
+ self.bridge_bin_path_edit = bridge.binary_path();
+ self.bridge_conn_line_edit = bridge.connection_line();
+ self.settings_changed = true;
+ }
}
if let Some(br) = TorConfig::get_bridge().as_ref() {
- // Show bridge binary setup.
- self.bridge_bin_ui(ui, br, cb);
-
- ui.add_space(10.0);
-
+ // Show bridge binary setup for non-Android.
+ if !is_android {
+ self.bridge_bin_ui(ui, br, cb);
+ ui.add_space(10.0);
+ }
// Show bridge connection line setup.
self.bridge_conn_line_ui(ui, br, cb);
}
@@ -210,29 +247,6 @@ impl ContentContainer for TorSettingsContent {
}
}
-impl Default for TorSettingsContent {
- fn default() -> Self {
- // Setup Tor bridge binary path edit text.
- let bridge = TorConfig::get_bridge();
- let (bin_path, conn_line) = if let Some(b) = bridge {
- (b.binary_path(), b.connection_line())
- } else {
- ("".to_string(), "".to_string())
- };
- Self {
- settings_changed: false,
- proxy_url_edit: "".to_string(),
- proxy_url_error: false,
- bridge_bin_path_edit: bin_path,
- bridge_bin_pick_file: FilePickContent::new(
- FilePickContentType::ItemButton(View::item_rounding(0, 1, true))
- ).no_parse(),
- bridge_conn_line_edit: conn_line,
- bridge_qr_scan_content: None,
- }
- }
-}
-
impl TorSettingsContent {
/// Draw proxy edit modal content.
fn proxy_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
@@ -363,8 +377,10 @@ impl TorSettingsContent {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
self.bridge_bin_pick_file.ui(ui, cb, |path| {
- TorBridge::save_bridge_bin_path(bridge, path);
- self.settings_changed = true;
+ if bridge.binary_path() != path {
+ TorBridge::save_bridge_bin_path(bridge, path);
+ self.settings_changed = true;
+ }
});
View::item_button(ui, View::item_rounding(1, 3, true), PENCIL, None, || {
self.bridge_bin_path_edit = bridge.binary_path();
@@ -396,7 +412,10 @@ impl TorSettingsContent {
fn bridge_bin_edit_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
let on_save = |c: &mut TorSettingsContent| {
let bridge = TorConfig::get_bridge().unwrap();
- TorBridge::save_bridge_bin_path(&bridge, c.bridge_bin_path_edit.clone());
+ if bridge.binary_path() != c.bridge_bin_path_edit {
+ TorBridge::save_bridge_bin_path(&bridge, c.bridge_bin_path_edit.clone());
+ c.settings_changed = true;
+ }
Modal::close();
};
@@ -502,7 +521,10 @@ impl TorSettingsContent {
fn bridge_conn_line_edit_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
let on_save = |c: &mut TorSettingsContent| {
let bridge = TorConfig::get_bridge().unwrap();
- TorBridge::save_bridge_conn_line(&bridge, c.bridge_conn_line_edit.clone());
+ if bridge.connection_line() != c.bridge_conn_line_edit {
+ TorBridge::save_bridge_conn_line(&bridge, c.bridge_conn_line_edit.clone());
+ c.settings_changed = true;
+ }
Modal::close();
};
diff --git a/src/gui/views/wallets/wallet/transport/content.rs b/src/gui/views/wallets/wallet/transport/content.rs
index 06524703..15cf822c 100644
--- a/src/gui/views/wallets/wallet/transport/content.rs
+++ b/src/gui/views/wallets/wallet/transport/content.rs
@@ -157,14 +157,14 @@ impl WalletTransportContent {
let is_running = Tor::is_service_running(service_id);
let has_error = Tor::is_service_failed(service_id);
- let address_color = if is_running {
+ let is_starting = Tor::is_service_starting(service_id);
+ let address_color = if is_running && !is_starting {
Colors::green()
} else if has_error {
Colors::red()
} else {
Colors::inactive_text()
};
- let is_starting = Tor::is_service_starting(service_id);
// Show slatepack address text.
View::animate_text(ui, addr.clone(), 17.0, address_color, is_starting);
ui.add_space(1.0);
diff --git a/src/gui/views/wallets/wallet/transport/settings.rs b/src/gui/views/wallets/wallet/transport/settings.rs
index 5453abf9..7bf9814c 100644
--- a/src/gui/views/wallets/wallet/transport/settings.rs
+++ b/src/gui/views/wallets/wallet/transport/settings.rs
@@ -12,22 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use egui::os::OperatingSystem;
use egui::RichText;
-use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::settings::TorSettingsContent;
use crate::gui::views::types::ContentContainer;
use crate::gui::views::View;
+use crate::gui::Colors;
use crate::tor::Tor;
use crate::wallet::Wallet;
/// Wallet transport settings content.
pub struct WalletTransportSettingsContent {
- /// Flag to check if settings were changed to restart Tor service.
- settings_changed: bool,
-
/// Tor transport content settings.
tor_settings_content: TorSettingsContent,
}
@@ -35,7 +31,6 @@ pub struct WalletTransportSettingsContent {
impl Default for WalletTransportSettingsContent {
fn default() -> Self {
Self {
- settings_changed: false,
tor_settings_content: TorSettingsContent::default(),
}
}
@@ -50,18 +45,11 @@ impl WalletTransportSettingsContent {
on_close: impl FnOnce()) {
ui.add_space(8.0);
ui.vertical_centered(|ui| {
- // Do not show bridges settings on Android.
- let os = OperatingSystem::from_target_os();
- if os != OperatingSystem::Android {
- // Show Tor settings.
- self.tor_settings_content.ui(ui, cb);
- if !self.tor_settings_content.settings_changed {
- self.settings_changed = true;
- }
- ui.add_space(4.0);
- View::horizontal_line(ui, Colors::item_stroke());
- ui.add_space(8.0);
- }
+ // Show Tor settings.
+ self.tor_settings_content.ui(ui, cb);
+ ui.add_space(4.0);
+ View::horizontal_line(ui, Colors::item_stroke());
+ ui.add_space(8.0);
ui.label(RichText::new(t!("transport.tor_autorun_desc"))
.size(17.0)
.color(Colors::inactive_text()));
@@ -69,14 +57,12 @@ impl WalletTransportSettingsContent {
let autorun = wallet.auto_start_tor_listener();
View::checkbox(ui, autorun, t!("network.autorun"), || {
wallet.update_auto_start_tor_listener(!autorun);
- self.settings_changed = true;
});
});
ui.add_space(8.0);
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
- if self.settings_changed {
- self.settings_changed = false;
+ if self.tor_settings_content.settings_changed {
// Restart running service or rebuild client.
let service_id = &wallet.identifier();
if Tor::is_service_running(service_id) {
diff --git a/src/lib.rs b/src/lib.rs
index be717885..bb4a95a0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -52,7 +52,7 @@ fn android_main(app: AndroidApp) {
{
std::env::set_var("RUST_BACKTRACE", "full");
let log_config = android_logger::Config::default()
- .with_max_level(log::LevelFilter::Debug)
+ .with_max_level(log::LevelFilter::Info)
.with_tag("grim");
android_logger::init_once(log_config);
}
diff --git a/src/settings/settings.rs b/src/settings/settings.rs
index 7a721ee6..49c23738 100644
--- a/src/settings/settings.rs
+++ b/src/settings/settings.rs
@@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use std::fs::{self, File};
-use std::io::Write;
-use std::path::PathBuf;
-use std::sync::Arc;
+use grin_config::ConfigError;
+use grin_core::global;
use lazy_static::lazy_static;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use serde::de::DeserializeOwned;
use serde::Serialize;
-use grin_config::ConfigError;
-use grin_core::global;
+use std::fs::{self, File};
+use std::io::Write;
+use std::path::PathBuf;
+use std::sync::Arc;
use crate::node::NodeConfig;
use crate::settings::AppConfig;
@@ -61,7 +61,8 @@ impl Settings {
// Initialize tor config.
let tor_config_path = Settings::config_path(TorConfig::FILE_NAME, None);
- let tor_config = Self::init_config::(tor_config_path);
+ let mut tor_config = Self::init_config::(tor_config_path);
+ tor_config.migrate();
// Setup chain type.
let chain_type = &app_config.chain_type;
@@ -183,10 +184,10 @@ impl Settings {
match parsed {
Ok(cfg) => Ok(cfg),
Err(e) => {
- return Err(ConfigError::ParseError(
+ Err(ConfigError::ParseError(
config_path.to_str().unwrap().to_string(),
format!("{}", e),
- ));
+ ))
}
}
}
diff --git a/src/tor/config.rs b/src/tor/config.rs
index 4feb4d4d..0269067d 100644
--- a/src/tor/config.rs
+++ b/src/tor/config.rs
@@ -18,6 +18,8 @@ use serde_derive::{Deserialize, Serialize};
use crate::Settings;
use crate::tor::{TorBridge, TorProxy};
+const TOR_CONFIG_VERSION: i32 = 1;
+
/// Tor configuration.
#[derive(Serialize, Deserialize, Clone)]
pub struct TorConfig {
@@ -30,19 +32,26 @@ pub struct TorConfig {
/// Selected bridge type.
bridge: Option,
+ /// Webtunnel bridge type.
+ webtunnel: TorBridge,
/// Obfs4 bridge type.
obfs4: TorBridge,
/// Snowflake bridge type.
snowflake: TorBridge,
+
+ /// Config version.
+ ver: Option
}
impl Default for TorConfig {
fn default() -> Self {
+ let webtunnel = Self::default_webtunnel_bridge();
Self {
proxy: None,
proxy_socks5: TorProxy::HTTP(TorProxy::DEFAULT_SOCKS5_URL.to_string()),
proxy_http: TorProxy::HTTP(TorProxy::DEFAULT_HTTP_URL.to_string()),
- bridge: None,
+ bridge: Some(webtunnel.clone()),
+ webtunnel,
obfs4: TorBridge::Obfs4(
TorBridge::DEFAULT_OBFS4_BIN_PATH.to_string(),
TorBridge::DEFAULT_OBFS4_CONN_LINE.to_string()
@@ -51,6 +60,7 @@ impl Default for TorConfig {
TorBridge::DEFAULT_SNOWFLAKE_BIN_PATH.to_string(),
TorBridge::DEFAULT_SNOWFLAKE_CONN_LINE.to_string()
),
+ ver: Some(TOR_CONFIG_VERSION),
}
}
}
@@ -69,14 +79,24 @@ impl TorConfig {
/// Subdirectory name for Tor keystore.
const KEYSTORE_DIR: &'static str = "keystore";
+ /// Webtunnel binary name.
+ pub const WEBTUNNEL_BIN: &'static str = "webtunnel";
+ /// Webtunnel Android binary name.
+ pub const WEBTUNNEL_ANDROID_BIN: &'static str = "libwebtunnel.so";
+
/// Save application configuration to the file.
pub fn save(&self) {
Settings::write_to_file(self, Settings::config_path(Self::FILE_NAME, None));
}
+ /// Get base Tor directory path.
+ fn base_path() -> PathBuf {
+ Settings::base_path(Some(Self::DIR_NAME.to_string()))
+ }
+
/// Get path from subdirectory name.
fn sub_dir_path(name: &str) -> String {
- let mut base = Settings::base_path(Some(Self::DIR_NAME.to_string()));
+ let mut base = Self::base_path();
base.push(name);
base.to_str().unwrap().to_string()
}
@@ -98,6 +118,31 @@ impl TorConfig {
base.to_str().unwrap().to_string()
}
+ /// Get default webtunnel bridge.
+ pub fn default_webtunnel_bridge() -> TorBridge {
+ TorBridge::Webtunnel(
+ if egui::os::OperatingSystem::from_target_os() == egui::os::OperatingSystem::Android {
+ "".to_string()
+ } else {
+ TorConfig::webtunnel_path()
+ },
+ TorBridge::DEFAULT_WEBTUNNEL_CONN_LINE.to_string()
+ )
+ }
+
+ /// Webtunnel binary path.
+ pub fn webtunnel_path() -> String {
+ let os = egui::os::OperatingSystem::from_target_os();
+ if os == egui::os::OperatingSystem::Android {
+ let base = std::env::var("NATIVE_LIBS_DIR").unwrap_or_default();
+ format!("{}/{}", base, Self::WEBTUNNEL_ANDROID_BIN)
+ } else {
+ let mut base = Self::base_path();
+ base.push(Self::WEBTUNNEL_BIN);
+ base.to_str().unwrap().to_string()
+ }
+ }
+
/// Save Tor bridge.
pub fn save_bridge(bridge: Option) {
let mut w_tor_config = Settings::tor_config_to_update();
@@ -105,12 +150,15 @@ impl TorConfig {
if bridge.is_some() {
let bridge = bridge.unwrap();
match &bridge {
- TorBridge::Snowflake(_, _) => {
- w_tor_config.snowflake = bridge
- }
- TorBridge::Obfs4(_, _) => {
- w_tor_config.obfs4 = bridge
- }
+ TorBridge::Webtunnel(_, _) => {
+ w_tor_config.webtunnel = bridge
+ }
+ TorBridge::Obfs4(_, _) => {
+ w_tor_config.obfs4 = bridge
+ }
+ TorBridge::Snowflake(_, _) => {
+ w_tor_config.snowflake = bridge
+ }
}
}
w_tor_config.save();
@@ -122,6 +170,12 @@ impl TorConfig {
r_config.bridge.clone()
}
+ /// Get saved Webtunnel bridge.
+ pub fn get_webtunnel() -> TorBridge {
+ let r_config = Settings::tor_config_to_read();
+ r_config.webtunnel.clone()
+ }
+
/// Get saved Obfs4 bridge.
pub fn get_obfs4() -> TorBridge {
let r_config = Settings::tor_config_to_read();
@@ -168,4 +222,17 @@ impl TorConfig {
let r_config = Settings::tor_config_to_read();
r_config.proxy_http.clone()
}
+
+ /// Check config version to migrate if needed.
+ pub fn migrate(&mut self) {
+ match self.ver {
+ None => {
+ // Migrate to 1st version.
+ self.bridge = Some(TorConfig::default_webtunnel_bridge());
+ self.ver = Some(1);
+ }
+ Some(_) => {}
+ }
+ self.save();
+ }
}
\ No newline at end of file
diff --git a/src/tor/tor.rs b/src/tor/tor.rs
index e226b023..c6432104 100644
--- a/src/tor/tor.rs
+++ b/src/tor/tor.rs
@@ -50,7 +50,7 @@ use tor_rtcompat::Runtime;
use crate::http::HttpClient;
use crate::tor::http::ArtiHttpConnector;
-use crate::tor::{TorConfig, TorProxy};
+use crate::tor::{TorBridge, TorConfig, TorProxy};
lazy_static! {
/// Static thread-aware state of [`Node`] to be updated from separate thread.
@@ -62,34 +62,37 @@ pub struct Tor {
/// Tor client and config.
client_config: Arc, TorClientConfig)>>,
/// Mapping of running Onion services identifiers to proxy.
- running_services:
- Arc, Arc)>>>,
+ run: Arc, Arc)>>>,
/// Starting Onion services identifiers.
- starting_services: Arc>>,
+ start: Arc>>,
/// Failed Onion services identifiers.
- failed_services: Arc>>,
+ fail: Arc>>,
/// Checking Onion services identifiers.
- checking_services: Arc>>,
+ check: Arc>>,
}
impl Default for Tor {
fn default() -> Self {
- // Cleanup keys, state and cache on start.
- 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();
+ // Extract webtunnel bridge binary.
+ if !fs::exists(TorConfig::webtunnel_path()).unwrap_or(true) {
+ let webtunnel = include_bytes!(concat!(env!("OUT_DIR"), "/tor/webtunnel"));
+ if !webtunnel.is_empty() {
+ fs::write(TorConfig::webtunnel_path(), webtunnel).unwrap_or_default();
+ }
+ }
+
// Create Tor client.
let runtime = TokioNativeTlsRuntime::create().unwrap();
- let config = Self::build_config();
+ let config = Self::build_config(true);
let client = TorClient::with_runtime(runtime)
.config(config.clone())
.create_unbootstrapped()
.unwrap();
Self {
- running_services: Arc::new(RwLock::new(BTreeMap::new())),
- starting_services: Arc::new(RwLock::new(BTreeSet::new())),
- failed_services: Arc::new(RwLock::new(BTreeSet::new())),
- checking_services: Arc::new(RwLock::new(BTreeSet::new())),
+ run: Arc::new(RwLock::new(BTreeMap::new())),
+ start: Arc::new(RwLock::new(BTreeSet::new())),
+ fail: Arc::new(RwLock::new(BTreeSet::new())),
+ check: Arc::new(RwLock::new(BTreeSet::new())),
client_config: Arc::new(RwLock::new((client, config))),
}
}
@@ -97,7 +100,13 @@ impl Default for Tor {
impl Tor {
/// Create Tor client configuration.
- fn build_config() -> TorClientConfig {
+ 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.
let mut builder = TorClientConfigBuilder::from_directories(
TorConfig::state_path(),
@@ -107,12 +116,7 @@ impl Tor {
// Setup bridges.
let bridge = TorConfig::get_bridge();
if let Some(b) = bridge {
- match b {
- super::TorBridge::Snowflake(path, conn) => {
- Self::build_snowflake(&mut builder, path, conn)
- }
- super::TorBridge::Obfs4(path, conn) => Self::build_obfs4(&mut builder, path, conn),
- }
+ Self::build_bridge(&mut builder, b);
}
// Create config.
let config = builder.build().unwrap();
@@ -121,7 +125,7 @@ impl Tor {
/// Recreate Tor client with configuration.
pub fn rebuild_client() {
- let config = Self::build_config();
+ let config = Self::build_config(false);
let r_client = TOR_SERVER_STATE.client_config.read();
r_client.0
.reconfigure(&config, tor_config::Reconfigure::AllOrNothing)
@@ -193,25 +197,25 @@ impl Tor {
/// Check if Onion service is starting.
pub fn is_service_starting(id: &String) -> bool {
- let r_services = TOR_SERVER_STATE.starting_services.read();
+ let r_services = TOR_SERVER_STATE.start.read();
r_services.contains(id)
}
/// Check if Onion service is running.
pub fn is_service_running(id: &String) -> bool {
- let r_services = TOR_SERVER_STATE.running_services.read();
+ let r_services = TOR_SERVER_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.failed_services.read();
+ let r_services = TOR_SERVER_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.checking_services.read();
+ let r_services = TOR_SERVER_STATE.check.read();
r_services.contains(id)
}
@@ -224,7 +228,7 @@ impl Tor {
/// Stop running Onion service.
pub fn stop_service(id: &String) {
- let mut w_services = TOR_SERVER_STATE.running_services.write();
+ let mut w_services = TOR_SERVER_STATE.run.write();
if let Some((svc, proxy)) = w_services.remove(id) {
proxy.shutdown();
drop(svc);
@@ -238,10 +242,10 @@ impl Tor {
return;
} else {
// Save starting service.
- let mut w_services = TOR_SERVER_STATE.starting_services.write();
+ 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.failed_services.write();
+ let mut w_services = TOR_SERVER_STATE.fail.write();
w_services.remove(id);
}
@@ -249,13 +253,32 @@ impl Tor {
thread::spawn(move || {
let on_error = |service_id: String| {
// Remove service from starting.
- let mut w_services = TOR_SERVER_STATE.starting_services.write();
+ let mut w_services = TOR_SERVER_STATE.start.write();
w_services.remove(&service_id);
// Save failed service.
- let mut w_services = TOR_SERVER_STATE.failed_services.write();
+ let mut w_services = TOR_SERVER_STATE.fail.write();
w_services.insert(service_id);
};
+ // Check bridge binary existence and permissions.
+ if let Some(bridge) = TorConfig::get_bridge() {
+ if !fs::exists(bridge.binary_path()).unwrap() {
+ on_error(service_id);
+ return;
+ }
+ // Add execute permission for Unix.
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ let mut perms = fs::metadata(bridge.binary_path())
+ .unwrap()
+ .permissions();
+ let mode = perms.mode() | 0o100;
+ perms.set_mode(mode);
+ fs::set_permissions(bridge.binary_path(), perms).unwrap_or_default();
+ }
+ }
+
let (client, config) = Self::client_config();
let client_thread = client.clone();
client
@@ -263,13 +286,15 @@ impl Tor {
.spawn(async move {
// Add service key to keystore.
let hs_nickname = HsNickname::new(service_id.clone()).unwrap();
- if let Err(_) = Self::add_service_key(config.fs_mistrust(), &key, &hs_nickname)
- {
+ if let Err(_) = Self::add_service_key(config.fs_mistrust(), &key, &hs_nickname) {
on_error(service_id);
return;
}
// Bootstrap client.
- client_thread.bootstrap().await.unwrap();
+ if let Err(_) = client_thread.bootstrap().await {
+ on_error(service_id);
+ return;
+ }
// Launch Onion service.
let service_config = OnionServiceConfigBuilder::default()
.nickname(hs_nickname.clone())
@@ -313,25 +338,23 @@ impl Tor {
}
let client_check = client.clone();
thread::spawn(move || {
- // Wait 1 second to start.
- thread::sleep(Duration::from_millis(1000));
+ // Wait 5 seconds to start.
+ thread::sleep(Duration::from_millis(5000));
let runtime = client.runtime();
// Put service to checking.
{
- let mut w_services = TOR_SERVER_STATE.checking_services.write();
+ let mut w_services = TOR_SERVER_STATE.check.write();
w_services.insert(service_id.clone());
}
runtime
.spawn(async move {
- let tls_conn =
- TlsConnector::builder().unwrap().build().unwrap();
- let tor_conn =
- ArtiHttpConnector::new(client_check.clone(), tls_conn);
- let http =
- hyper_tor::Client::builder().build::<_, hyper_tor::Body>(tor_conn);
+ let tls_conn = TlsConnector::builder().unwrap().build().unwrap();
+ let tor_conn = ArtiHttpConnector::new(client_check.clone(), tls_conn);
+ let http = hyper_tor::Client::builder().build::<_, hyper_tor::Body>(tor_conn);
const MAX_ERRORS: i32 = 3;
let mut errors_count = 0;
+ let mut first_start = true;
loop {
// Check if service is running.
fn is_running(service_id: &String) -> bool {
@@ -339,7 +362,7 @@ impl Tor {
if !running {
// Remove service from checking.
let mut w_services =
- TOR_SERVER_STATE.checking_services.write();
+ TOR_SERVER_STATE.check.write();
w_services.remove(service_id);
}
running
@@ -347,45 +370,75 @@ impl Tor {
if !is_running(&service_id) {
break;
}
- // Send request.
- let duration = match http
- .get(hyper_tor::Uri::from_str(url.clone().as_str()).unwrap())
- .await
- {
- Ok(_) => {
- // Remove service from starting.
- let mut w_services =
- TOR_SERVER_STATE.starting_services.write();
- w_services.remove(&service_id);
- // Remove service from failed.
- let mut w_services =
- TOR_SERVER_STATE.failed_services.write();
- w_services.remove(&service_id);
- if !is_running(&service_id) {
- break;
- }
- // Check again after 50 seconds.
- Duration::from_millis(50000)
+ // Put service to starting.
+ if first_start {
+ {
+ let mut w_services = TOR_SERVER_STATE.start.write();
+ w_services.insert(service_id.clone());
}
- Err(_) => {
- if !is_running(&service_id) {
- break;
+ }
+ // Send request.
+ let duration = {
+ let uri = hyper_tor::Uri::from_str(url.clone().as_str()).unwrap();
+ let check = http.get(uri);
+ let mut on_error = |service_id: &String| -> bool {
+ if !is_running(service_id) {
+ return true;
}
// Restart service on 3rd error.
errors_count += 1;
if errors_count == MAX_ERRORS {
- errors_count = 0;
+ // 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 service_id = service_id.clone();
+ let id = service_id.clone();
thread::spawn(move || {
- Self::restart_service(
- port,
- key,
- &service_id,
- );
+ Self::restart_service(port, key, &id);
});
+ return true;
+ }
+ false
+ };
+ // 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) {
+ break;
+ }
+ // Remove service from starting.
+ if first_start {
+ let mut w_services = TOR_SERVER_STATE.start.write();
+ w_services.remove(&service_id);
+ first_start = false;
+ }
+ errors_count = 0;
+ // Check again after 60s.
+ Duration::from_millis(60000)
+ }
+ Err(_) => {
+ if on_error(&service_id) {
+ break;
+ }
+ // Check again after 10s.
+ Duration::from_millis(10000)
+ }
+ }
+ }
+ Err(_) => {
+ if on_error(&service_id) {
+ break;
+ }
+ // Check again after 10s.
+ Duration::from_millis(10000)
}
- Duration::from_millis(5000)
}
};
// Wait to check service again.
@@ -419,30 +472,32 @@ impl Tor {
proxy_cfg_builder.set_proxy_ports(vec![proxy_rule]);
let proxy = OnionServiceReverseProxy::new(proxy_cfg_builder.build().unwrap());
+ // Remove service from failed.
+ let mut w_services = TOR_SERVER_STATE.fail.write();
+ w_services.remove(&id);
// Save running service.
- let mut w_services = TOR_SERVER_STATE.running_services.write();
+ let mut w_services = TOR_SERVER_STATE.run.write();
w_services.insert(id.clone(), (service.clone(), proxy.clone()));
// Start proxy for launched service.
client
.runtime()
.spawn(async move {
- match proxy
- .handle_requests(runtime, nickname.clone(), request)
- .await
- {
+ match proxy.handle_requests(runtime, nickname.clone(), request).await {
Ok(()) => {
// Remove service from running.
- let mut w_services = TOR_SERVER_STATE.running_services.write();
+ let mut w_services = TOR_SERVER_STATE.run.write();
w_services.remove(&id);
}
Err(_) => {
- // Remove service from running.
- let mut w_services = TOR_SERVER_STATE.running_services.write();
- w_services.remove(&id);
- // Save failed service.
- let mut w_services = TOR_SERVER_STATE.failed_services.write();
- w_services.insert(id);
+ if Self::is_service_running(&id) {
+ // Remove service from running.
+ let mut w_services = TOR_SERVER_STATE.run.write();
+ w_services.remove(&id);
+ // Save failed service.
+ let mut w_services = TOR_SERVER_STATE.fail.write();
+ w_services.insert(id);
+ }
}
}
})
@@ -487,36 +542,19 @@ impl Tor {
Ok(())
}
- fn build_snowflake(builder: &mut TorClientConfigBuilder, bin_path: String, conn_line: String) {
- let bridge_line = format!("Bridge {}", conn_line);
+ fn build_bridge(builder: &mut TorClientConfigBuilder, bridge: TorBridge) {
+ let bridge_line = format!("Bridge {}", bridge.connection_line());
if let Ok(bridge) = bridge_line.parse() {
builder.bridges().bridges().push(bridge);
}
- // Now configure a snowflake transport. (Requires the "pt-client" feature)
+ // Now configure bridge 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.
- .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, 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()])
+ .protocols(vec![bridge.protocol_name().parse().unwrap()])
// 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()))
+ .path(CfgPath::new(bridge.binary_path().into()))
.run_on_startup(true);
builder.bridges().transports().push(transport);
}
diff --git a/src/tor/types.rs b/src/tor/types.rs
index 4b859878..049a3c66 100644
--- a/src/tor/types.rs
+++ b/src/tor/types.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use egui::os;
use serde_derive::{Deserialize, Serialize};
use crate::tor::TorConfig;
@@ -42,6 +43,8 @@ impl TorProxy {
/// Tor network bridge type.
#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub enum TorBridge {
+ /// Obfs4 bridge with binary path and connection line.
+ Webtunnel(String, String),
/// Obfs4 bridge with binary path and connection line.
Obfs4(String, String),
/// Snowflake bridge with binary path and connection line.
@@ -54,6 +57,8 @@ impl TorBridge {
/// Default Snowflake protocol client binary path.
pub const DEFAULT_SNOWFLAKE_BIN_PATH: &'static str = "/usr/bin/snowflake-client";
+ /// 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";
/// 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.
@@ -62,14 +67,21 @@ impl TorBridge {
/// Get bridge protocol name.
pub fn protocol_name(&self) -> String {
match *self {
+ TorBridge::Webtunnel(_, _) => "webtunnel".to_string(),
TorBridge::Obfs4(_, _) => "obfs4".to_string(),
- TorBridge::Snowflake(_, _) => "snowflake".to_string()
+ TorBridge::Snowflake(_, _) => "snowflake".to_string(),
}
}
/// Get bridge client binary path.
pub fn binary_path(&self) -> String {
+ let is_android = os::OperatingSystem::from_target_os() == os::OperatingSystem::Android;
match self {
+ TorBridge::Webtunnel(path, _) => if is_android {
+ TorConfig::webtunnel_path()
+ } else {
+ path.clone()
+ },
TorBridge::Obfs4(path, _) => path.clone(),
TorBridge::Snowflake(path, _) => path.clone()
}
@@ -78,6 +90,7 @@ impl TorBridge {
/// Get bridge client connection line.
pub fn connection_line(&self) -> String {
match self {
+ TorBridge::Webtunnel(_, line) => line.clone(),
TorBridge::Obfs4(_, line) => line.clone(),
TorBridge::Snowflake(_, line) => line.clone()
}
@@ -86,6 +99,9 @@ impl TorBridge {
/// Save binary path to provided bridge.
pub fn save_bridge_bin_path(bridge: &TorBridge, path: String) {
match bridge {
+ TorBridge::Webtunnel(_, line) => {
+ TorConfig::save_bridge(Some(TorBridge::Webtunnel(path, line.into())));
+ }
TorBridge::Obfs4(_, line) => {
TorConfig::save_bridge(Some(TorBridge::Obfs4(path, line.into())));
}
@@ -98,6 +114,9 @@ impl TorBridge {
/// Save connection line to provided bridge.
pub fn save_bridge_conn_line(bridge: &TorBridge, line: String) {
match bridge {
+ TorBridge::Webtunnel(path, _) => {
+ TorConfig::save_bridge(Some(TorBridge::Webtunnel(path.into(), line)));
+ }
TorBridge::Obfs4(path, _) => {
TorConfig::save_bridge(
Some(TorBridge::Obfs4(path.into(), line))
diff --git a/tor/webtunnel b/tor/webtunnel
new file mode 160000
index 00000000..8814d4ef
--- /dev/null
+++ b/tor/webtunnel
@@ -0,0 +1 @@
+Subproject commit 8814d4ef97c4d8c1cf9893a209179ac74fdecf57
diff --git a/wallet b/wallet
index 5c5d1492..c51433d0 160000
--- a/wallet
+++ b/wallet
@@ -1 +1 @@
-Subproject commit 5c5d1492741410c0b74dde9c23ade155a64a0dd4
+Subproject commit c51433d02b4d7df246f82a427fd19cdb0b38e608