onionwire/src/hs.rs

87 lines
3.1 KiB
Rust
Raw Normal View History

//! In-process Arti onion-service helpers (no C-tor).
use std::sync::Arc;
use std::time::{Duration, Instant};
use arti_client::config::TorClientConfigBuilder;
use arti_client::{TorClient, TorClientConfig};
use futures::StreamExt;
use safelog::DisplayRedacted;
use tor_hsservice::status::State;
use tor_hsservice::{HsNickname, OnionServiceConfig, RunningOnionService};
use tor_rtcompat::PreferredRuntime;
pub const HS_PORT: u16 = 80;
/// HsDir descriptor upload often stays Bootstrapping past 3 minutes.
pub const PUBLISH_WAIT: Duration = Duration::from_secs(360);
/// Consensus default `cbtmintimeout` is 10ms; learned CBT can drop to ~1s.
pub const CBT_MIN_TIMEOUT_MS: i32 = 10_000;
pub type Client = Arc<TorClient<PreferredRuntime>>;
pub fn client_config(state_dir: &std::path::Path, cache_dir: &std::path::Path) -> TorClientConfig {
std::fs::create_dir_all(state_dir).expect("state dir");
std::fs::create_dir_all(cache_dir).expect("cache dir");
let mut builder = TorClientConfigBuilder::from_directories(state_dir, cache_dir);
builder.storage().permissions().dangerously_trust_everyone();
builder
.override_net_params()
.insert("cbtmintimeout".to_string(), CBT_MIN_TIMEOUT_MS);
builder.build().expect("TorClientConfig")
}
pub async fn bootstrapped(
state_dir: &std::path::Path,
cache_dir: &std::path::Path,
) -> Result<Client, String> {
let cfg = client_config(state_dir, cache_dir);
TorClient::create_bootstrapped(cfg)
.await
.map_err(|e| format!("Arti bootstrap failed — fail closed, no C-tor fallback: {e}"))
}
pub fn hs_config(nickname: &str) -> Result<OnionServiceConfig, String> {
let nickname = HsNickname::new(nickname.to_string()).map_err(|e| format!("HsNickname: {e}"))?;
OnionServiceConfig::builder()
.nickname(nickname)
.build()
.map_err(|e| format!("OnionServiceConfig: {e}"))
}
pub fn onion_string(svc: &RunningOnionService) -> Result<String, String> {
let id = svc
.onion_address()
.ok_or_else(|| "onion identity missing from keystore".to_string())?;
Ok(id.display_unredacted().to_string())
}
pub async fn wait_until_published(svc: &RunningOnionService, label: &str) -> Result<(), String> {
let deadline = Instant::now() + PUBLISH_WAIT;
let mut events = svc.status_events();
loop {
let st = svc.status();
eprintln!("{label} hs status: {:?}", st.state());
match st.state() {
State::Running | State::DegradedReachable => return Ok(()),
State::Broken => {
return Err(format!(
"{label}: onion service broken: {:?}",
st.current_problem()
));
}
_ => {}
}
if Instant::now() >= deadline {
return Err(format!(
"{label}: onion service did not publish within {}s: {:?}",
PUBLISH_WAIT.as_secs(),
st.state()
));
}
tokio::select! {
_ = events.next() => {}
_ = tokio::time::sleep(Duration::from_secs(2)) => {}
}
}
}