tor: launch services

This commit is contained in:
ardocrat 2024-04-27 02:19:40 +03:00
parent 1e6376c497
commit 744b7955c1
6 changed files with 379 additions and 23 deletions

161
Cargo.lock generated
View file

@ -521,6 +521,7 @@ dependencies = [
"tor-guardmgr",
"tor-hsclient",
"tor-hscrypto",
"tor-hsservice",
"tor-keymgr",
"tor-linkspec",
"tor-llcrypto",
@ -2966,6 +2967,27 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "fslock-arti-fork"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b21bd626aaab7b904b20bef6d9e06298914a0c8d9fb8b010483766b2e532791"
dependencies = [
"libc",
"winapi 0.3.9",
]
[[package]]
name = "fslock-guard"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9267d03223dd8877b0a3f8341661d21b7ba6a18e90f60e92e550addd30bc32c7"
dependencies = [
"fslock-arti-fork",
"thiserror",
"winapi 0.3.9",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@ -3394,7 +3416,9 @@ dependencies = [
"arti-client",
"built",
"chrono",
"curve25519-dalek 4.1.2",
"dirs 5.0.1",
"ed25519-dalek 2.1.1",
"eframe",
"egui",
"egui_extras",
@ -3425,12 +3449,18 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"sha2 0.10.8",
"sys-locale",
"thiserror",
"tokio 1.37.0",
"tokio-util 0.7.10",
"toml 0.8.12",
"tor-config",
"tor-hscrypto",
"tor-hsrproxy",
"tor-hsservice",
"tor-keymgr",
"tor-llcrypto",
"tor-rtcompat",
"url",
"wgpu",
@ -3872,6 +3902,18 @@ dependencies = [
"subtle",
]
[[package]]
name = "growable-bloom-filter"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c669fa03050eb3445343f215d62fc1ab831e8098bc9a55f26e9724faff11075c"
dependencies = [
"serde",
"serde_bytes",
"serde_derive",
"xxhash-rust",
]
[[package]]
name = "h2"
version = "0.2.7"
@ -4636,6 +4678,16 @@ dependencies = [
"serde_json",
]
[[package]]
name = "k12"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4dc5fdb62af2f520116927304f15d25b3c2667b4817b90efdc045194c912c54"
dependencies = [
"digest 0.10.7",
"sha3 0.10.8",
]
[[package]]
name = "keccak"
version = "0.1.5"
@ -6528,6 +6580,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
[[package]]
name = "rangemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684"
[[package]]
name = "rav1e"
version = "0.7.1"
@ -7267,6 +7325,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_bytes"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.198"
@ -8397,6 +8464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87bb9b74a5f5402768cee442147641d39ca2d0cba459f52fcca03cd8d978bd0d"
dependencies = [
"caret",
"derive_builder_fork_arti",
"derive_more",
"digest 0.10.7",
"thiserror",
@ -8724,6 +8792,7 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b0c899ce91d6fe6461f646d1e3c8d421dd5c8b570c0799540c4d4a2de80013"
dependencies = [
"cipher 0.4.4",
"data-encoding",
"derive_more",
"digest 0.10.7",
@ -8741,6 +8810,90 @@ dependencies = [
"tor-error",
"tor-llcrypto",
"tor-units",
"zeroize",
]
[[package]]
name = "tor-hsrproxy"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e66cbbbff869500e673c775a056e0ead78152ebbfbe51ce442bd9833b873882"
dependencies = [
"derive-adhoc 0.8.4",
"derive_builder_fork_arti",
"futures 0.3.30",
"rangemap",
"safelog",
"serde",
"serde_with",
"thiserror",
"tor-async-utils",
"tor-cell",
"tor-config",
"tor-error",
"tor-hsservice",
"tor-log-ratelim",
"tor-proto",
"tor-rtcompat",
"tracing",
"void",
]
[[package]]
name = "tor-hsservice"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f81310c95fa1cd2d533dd1cf7ef43720308ac5bd0086801409491ebc1130c6e"
dependencies = [
"amplify",
"async-trait",
"base64ct",
"derive-adhoc 0.8.4",
"derive_builder_fork_arti",
"derive_more",
"digest 0.10.7",
"educe",
"fs-mistrust",
"futures 0.3.30",
"growable-bloom-filter",
"hex",
"humantime 2.1.0",
"itertools 0.12.1",
"k12",
"once_cell",
"postage",
"rand 0.8.5",
"rand_core 0.6.4",
"retry-error",
"safelog",
"serde",
"serde_with",
"strum 0.26.2",
"thiserror",
"tor-async-utils",
"tor-basic-utils",
"tor-bytes",
"tor-cell",
"tor-cert",
"tor-circmgr",
"tor-config",
"tor-dirclient",
"tor-error",
"tor-hscrypto",
"tor-keymgr",
"tor-linkspec",
"tor-llcrypto",
"tor-log-ratelim",
"tor-netdir",
"tor-netdoc",
"tor-persist",
"tor-proto",
"tor-protover",
"tor-relay-selection",
"tor-rtcompat",
"tor-units",
"tracing",
"void",
]
[[package]]
@ -8934,11 +9087,13 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7401ddb8c6a9ed71adeab421a20b786196409f02f389b415bb56041a5b0c80d4"
dependencies = [
"amplify",
"derive-adhoc 0.8.4",
"derive_more",
"filetime",
"fs-mistrust",
"fslock",
"fslock-guard",
"itertools 0.12.1",
"paste",
"sanitize-filename",
@ -10384,6 +10539,12 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
[[package]]
name = "xxhash-rust"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03"
[[package]]
name = "xz2"
version = "0.1.7"

View file

@ -56,10 +56,18 @@ tokio = { version = "1.37.0", features = ["full"] }
## tor
arti = { version = "1.2.0", features = ["experimental-api", "pt-client", "static"] }
arti-client = { version = "0.17.0", features = ["experimental-api", "pt-client", "static"] }
arti-client = { version = "0.17.0", features = ["experimental-api", "pt-client", "static", "onion-service-service"] }
tor-rtcompat = { version = "0.17.0", features = ["static"] }
tor-config = "0.17.0"
fs-mistrust = "0.7.9"
tor-hsservice = "0.17.0"
tor-hsrproxy = "0.17.0"
tor-keymgr = "0.17.0"
ed25519-dalek = "2.1.1"
tor-llcrypto = "0.17.0"
tor-hscrypto = "0.17.0"
sha2 = "0.10.0"
curve25519-dalek = "4.1.2"
## stratum server
tokio-util = { version = "0.7.8", features = ["codec"] }

View file

@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::time::Duration;
use egui::{Align, Id, Layout, RichText, Rounding};
use url::Url;
use crate::tor::{TorServer, TorServerConfig};
use crate::AppConfig;
use crate::gui::Colors;
@ -22,7 +24,6 @@ use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, NodeSetup, View};
use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions};
use crate::node::{Node, NodeConfig};
use crate::tor::{TorServer, TorServerConfig};
use crate::wallet::{ConnectionsConfig, ExternalConnection};
/// Network connections content.
@ -122,6 +123,12 @@ impl ConnectionsContent {
});
}
}
// Redraw after delay if Tor server is running.
if TorServer::is_running() || TorServer::is_starting() ||
TorServer::is_stopping() {
ui.ctx().request_repaint_after(Duration::from_millis(1000));
}
}
/// Draw Tor connection item content.

View file

@ -16,13 +16,12 @@ use egui::{Margin, RichText, ScrollArea, Stroke};
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{BRIEFCASE, CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, PLUS_CIRCLE, POWER};
use crate::gui::icons::{BRIEFCASE, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, PLUS_CIRCLE, POWER};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{ConnectionsContent, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings, Root, TitlePanel, View};
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
use crate::gui::views::types::{TitleContentType, TitleType};
use crate::node::Node;
use crate::tor::TorServer;
use crate::wallet::ExternalConnection;
/// Network content.
@ -148,8 +147,7 @@ impl NetworkContent {
});
// Redraw after delay if node is syncing to update stats.
if Node::is_running() || TorServer::is_running() || TorServer::is_starting() ||
TorServer::is_stopping() {
if Node::is_running() {
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
}
}
@ -219,7 +217,7 @@ impl NetworkContent {
self.connections.show_add_ext_conn_modal(None, cb);
});
}
}, |ui, frame| {
}, |ui, _| {
if !Root::is_dual_panel_mode(ui) {
View::title_button(ui, BRIEFCASE, || {
Root::toggle_network_panel();

View file

@ -33,12 +33,45 @@ impl Default for TorServerConfig {
}
impl TorServerConfig {
/// Application configuration file name.
pub const FILE_NAME: &'static str = "app.toml";
/// Tor configuration file name.
pub const FILE_NAME: &'static str = "tor.toml";
/// Directory for config and Tor related files.
const DIR_NAME: &'static str = "tor";
/// Subdirectory name for Tor state.
const STATE_SUB_DIR: &'static str = "state";
/// Subdirectory name for Tor cache.
const CACHE_SUB_DIR: &'static str = "cache";
/// Subdirectory name for Tor keystore.
const KEYSTORE_DIR: &'static str = "keystore";
/// Save application configuration to the file.
pub fn save(&self) {
Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, None));
Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME,
Some(Self::DIR_NAME.to_string())));
}
/// Get subdirectory path from dir name.
fn sub_dir_path(name: &str) -> String {
let mut base = Settings::get_base_path(Some(Self::DIR_NAME.to_string()));
base.push(name);
base.to_str().unwrap().to_string()
}
/// Get Tor state directory path.
pub fn state_path() -> String {
Self::sub_dir_path(Self::STATE_SUB_DIR)
}
/// Get Tor cache directory path.
pub fn cache_path() -> String {
Self::sub_dir_path(Self::CACHE_SUB_DIR)
}
/// Get Tor keystore directory path.
pub fn keystore_path() -> String {
Self::sub_dir_path(Self::KEYSTORE_DIR)
}
/// Get SOCKS port value.

View file

@ -12,22 +12,37 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::{Arc, RwLock};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
use lazy_static::lazy_static;
use arti::socks::run_socks_proxy;
use arti_client::{TorClient, TorClientConfig};
use arti_client::config::pt::{TransportConfigBuilder};
use arti_client::config::{BridgeConfigBuilder, TorClientConfigBuilder, StorageConfigBuilder};
use futures::task::SpawnExt;
use tokio::task::JoinHandle;
use anyhow::{Result};
use anyhow::Result;
use tokio::time::sleep;
use arti::socks::run_socks_proxy;
use arti_client::{TorClient, TorClientConfig};
use arti_client::config::pt::TransportConfigBuilder;
use arti_client::config::{BridgeConfigBuilder, TorClientConfigBuilder};
use fs_mistrust::Mistrust;
use grin_util::secp::SecretKey;
use grin_wallet_util::OnionV3Address;
use ed25519_dalek::hazmat::ExpandedSecretKey;
use curve25519_dalek::digest::Digest;
use sha2::Sha512;
use tor_config::{CfgPath, Listen};
use tor_rtcompat::{BlockOn, Runtime};
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
use tor_rtcompat::{BlockOn, PreferredRuntime, Runtime};
use tor_hsrproxy::OnionServiceReverseProxy;
use tor_hsrproxy::config::{Encapsulation, ProxyAction, ProxyPattern, ProxyRule, TargetAddr, ProxyConfigBuilder};
use tor_hsservice::config::OnionServiceConfigBuilder;
use tor_hsservice::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname};
use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector};
use tor_llcrypto::pk::ed25519::ExpandedKeypair;
use tor_hscrypto::pk::{HsIdKey, HsIdKeypair};
use crate::tor::TorServerConfig;
@ -36,18 +51,25 @@ lazy_static! {
static ref TOR_SERVER_STATE: Arc<TorServer> = Arc::new(TorServer::default());
}
/// Tor SOCKS proxy server.
/// Tor server to use as SOCKS proxy for requests and to launch Onion services.
pub struct TorServer {
/// Running Tor client.
client: Arc<RwLock<Option<TorClient<PreferredRuntime>>>>,
/// Running Tor client configuration.
config: Arc<RwLock<Option<TorClientConfig>>>,
/// Flag to check if server is running.
running: AtomicBool,
/// Flag to check if server is starting.
starting: AtomicBool,
/// Flag to check if server needs to stop.
stopping: AtomicBool,
/// Flag to check if error happened.
error: AtomicBool,
/// Tor client to use for proxy.
client: Arc<RwLock<Option<TorClient<TokioNativeTlsRuntime>>>>
/// Mapping of running Onion services identifiers to proxy.
running_services: Arc<RwLock<HashMap<String, Arc<OnionServiceReverseProxy>>>>
}
impl Default for TorServer {
@ -58,6 +80,8 @@ impl Default for TorServer {
stopping: AtomicBool::new(false),
error: AtomicBool::new(false),
client: Arc::new(RwLock::new(None)),
running_services: Arc::new(RwLock::new(HashMap::new())),
config: Arc::new(RwLock::new(None)),
}
}
}
@ -109,13 +133,19 @@ impl TorServer {
let _ = runtime.clone().block_on(Self::launch_socks_proxy(runtime, client));
} else {
// Create Tor client config to connect.
let mut builder = TorClientConfig::builder();
let mut builder =
TorClientConfigBuilder::from_directories(TorServerConfig::state_path(),
TorServerConfig::cache_path());
builder.address_filter().allow_onion_addrs(true);
// Setup Snowflake bridges.
Self::setup_bridges(&mut builder);
// Create Tor client from config.
if let Ok(config) = builder.build() {
let mut w_config = TOR_SERVER_STATE.config.write().unwrap();
*w_config = Some(config.clone());
// Restart server on connection timeout.
thread::spawn(|| {
thread::sleep(Duration::from_millis(30000));
@ -125,7 +155,7 @@ impl TorServer {
}
});
// Create Tor client.
let runtime = TokioNativeTlsRuntime::create().unwrap();
let runtime = PreferredRuntime::current().unwrap();
match TorClient::with_runtime(runtime.clone())
.config(config)
.bootstrap_behavior(arti_client::BootstrapBehavior::OnDemand)
@ -155,7 +185,7 @@ impl TorServer {
});
}
/// Launch SOCKS proxy server.
/// Launch SOCKS proxy server to send connections.
async fn launch_socks_proxy<R: Runtime>(runtime: R, tor_client: TorClient<R>) -> Result<()> {
let proxy_handle: JoinHandle<Result<()>> = tokio::spawn(
run_socks_proxy(
@ -180,6 +210,125 @@ impl TorServer {
}
}
/// Check if Onion service is running.
pub fn is_service_running(id: &String) -> bool {
let r_services = TOR_SERVER_STATE.running_services.read().unwrap();
r_services.contains_key(id)
}
/// Stop running Onion service.
pub fn stop_service(id: &String) {
let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap();
if let Some(proxy) = w_services.remove(id) {
proxy.shutdown();
}
}
/// Run Onion service from listening local address, secret key and identifier.
pub fn run_service(addr: SocketAddr, key: SecretKey, id: &String) {
// Check if service is already running.
if Self::is_service_running(id) {
return;
}
let hs_nickname = HsNickname::new(id.clone()).unwrap();
let service_config = OnionServiceConfigBuilder::default()
.nickname(hs_nickname.clone())
.build()
.unwrap();
let r_client = TOR_SERVER_STATE.client.read().unwrap();
let client = r_client.clone().unwrap();
// Add service key to keystore.
let r_config = TOR_SERVER_STATE.config.read().unwrap();
let config = r_config.clone().unwrap();
Self::add_service_key(config.fs_mistrust(), &key, &hs_nickname);
// Launch Onion service.
let (_, request) = client.launch_onion_service(service_config).unwrap();
// Setup proxy to forward request from Tor address to local address.
let proxy_rule = ProxyRule::new(
ProxyPattern::one_port(80).unwrap(),
ProxyAction::Forward(Encapsulation::Simple, TargetAddr::Inet(addr)),
);
let mut proxy_cfg_builder = ProxyConfigBuilder::default();
proxy_cfg_builder.set_proxy_ports(vec![proxy_rule]);
let proxy = OnionServiceReverseProxy::new(proxy_cfg_builder.build().unwrap());
// Launch proxy at client runtime.
let proxy_service = proxy.clone();
let runtime = client.runtime().clone();
let nickname = hs_nickname.clone();
client
.runtime()
.spawn(async move {
// Launch proxy for launched service.
match proxy_service.handle_requests(runtime, nickname.clone(), request).await {
Ok(()) => {
eprintln!("Onion service {} stopped.", nickname);
}
Err(e) => {
eprintln!("Onion service {} exited with an error: {}", nickname, e);
}
}
}).unwrap();
// Save running service.
let mut w_services = TOR_SERVER_STATE.running_services.write().unwrap();
w_services.insert(id.clone(), proxy);
let onion_addr = OnionV3Address::from_private(&key.0).unwrap();
eprintln!("Onion service {} launched at {}", hs_nickname, onion_addr.to_ov3_str());
}
/// Add Onion service key to keystore.
fn add_service_key(mistrust: &Mistrust, key: &SecretKey, hs_nickname: &HsNickname) {
let mut client_config_builder = TorClientConfigBuilder::from_directories(
TorServerConfig::state_path(),
TorServerConfig::cache_path()
);
client_config_builder
.address_filter()
.allow_onion_addrs(true);
let arti_store =
ArtiNativeKeystore::from_path_and_mistrust(TorServerConfig::keystore_path(), &mistrust)
.unwrap();
let key_manager = KeyMgrBuilder::default()
.default_store(Box::new(arti_store))
.build()
.unwrap();
let expanded_sk = ExpandedSecretKey::from_bytes(
Sha512::default()
.chain_update(key)
.finalize()
.as_ref(),
);
let mut sk_bytes = [0_u8; 64];
sk_bytes[0..32].copy_from_slice(&expanded_sk.scalar.to_bytes());
sk_bytes[32..64].copy_from_slice(&expanded_sk.hash_prefix);
let expanded_kp = ExpandedKeypair::from_secret_key_bytes(sk_bytes).unwrap();
key_manager
.insert(
HsIdKey::from(expanded_kp.public().clone()),
&HsIdPublicKeySpecifier::new(hs_nickname.clone()),
KeystoreSelector::Default,
)
.unwrap();
key_manager
.insert(
HsIdKeypair::from(expanded_kp),
&HsIdKeypairSpecifier::new(hs_nickname.clone()),
KeystoreSelector::Default,
)
.unwrap();
}
/// Setup Tor Snowflake bridges.
fn setup_bridges(builder: &mut TorClientConfigBuilder) {
// Add a single bridge to the list of bridges, from a bridge line.