From dcd458ab532effa9606c87d554a62a8d86edb55f Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 10 Sep 2026 13:30:05 -0400 Subject: [PATCH] wip(hs): publish-wait ceiling + CBT floor for Arti descriptor upload Rescued from the uncommitted working tree on main. Not reviewed, not tested against the network: - PUBLISH_WAIT 180s -> 420s (Arti's per-HsDir upload timeout is 5m) - CBT_MIN_TIMEOUT_MSEC = 10_000 via override_net_params cbtmintimeout - status line now prints elapsed/publish-wait and current_problem() - tests/tor_hs.rs adjusted to match --- src/hs.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++---- tests/tor_hs.rs | 13 ++++------- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/hs.rs b/src/hs.rs index 10f9212..d34c1e3 100644 --- a/src/hs.rs +++ b/src/hs.rs @@ -12,14 +12,30 @@ use tor_hsservice::{HsNickname, OnionServiceConfig, RunningOnionService}; use tor_rtcompat::PreferredRuntime; pub const HS_PORT: u16 = 80; +/// Ceiling for waiting on `Running` / `DegradedReachable`. +/// +/// Arti's per-HsDir upload timeout is 5 minutes, and circuit timeouts keep +/// the combined status at `Bootstrapping` well past 3 minutes. Keep this +/// at or above 360s so first publish can finish. +pub const PUBLISH_WAIT: Duration = Duration::from_secs(420); +/// Floor for Arti circuit-build timeout (ms). Learned CBT on this host +/// dropped to ~1s, which CircTimeouts every HsDir descriptor upload. +pub const CBT_MIN_TIMEOUT_MSEC: i32 = 10_000; pub type Client = Arc>; +fn apply_net_overrides(builder: &mut TorClientConfigBuilder) { + builder + .override_net_params() + .insert("cbtmintimeout".to_string(), CBT_MIN_TIMEOUT_MSEC); +} + 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(); + apply_net_overrides(&mut builder); builder.build().expect("TorClientConfig") } @@ -49,11 +65,17 @@ pub fn onion_string(svc: &RunningOnionService) -> Result { } pub async fn wait_until_published(svc: &RunningOnionService, label: &str) -> Result<(), String> { - let deadline = Instant::now() + Duration::from_secs(180); + let started = Instant::now(); + let deadline = started + PUBLISH_WAIT; let mut events = svc.status_events(); loop { let st = svc.status(); - eprintln!("{label} hs status: {:?}", st.state()); + eprintln!( + "{label} hs status: {:?} ({:.0}s/{}s)", + st.state(), + started.elapsed().as_secs_f32(), + PUBLISH_WAIT.as_secs() + ); match st.state() { State::Running | State::DegradedReachable => return Ok(()), State::Broken => { @@ -66,8 +88,10 @@ pub async fn wait_until_published(svc: &RunningOnionService, label: &str) -> Res } if Instant::now() >= deadline { return Err(format!( - "{label}: onion service did not publish within 180s: {:?}", - st.state() + "{label}: onion service did not publish within {}s: {:?} problem={:?}", + PUBLISH_WAIT.as_secs(), + st.state(), + st.current_problem() )); } tokio::select! { @@ -76,3 +100,30 @@ pub async fn wait_until_published(svc: &RunningOnionService, label: &str) -> Res } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn publish_wait_covers_hsdir_upload_retries() { + assert!( + PUBLISH_WAIT >= Duration::from_secs(360), + "HsDir circuit timeouts can keep Bootstrapping ~6min before DegradedReachable" + ); + } + + #[test] + fn cbt_min_timeout_overrides_learned_subsecond_estimate() { + let mut builder = TorClientConfigBuilder::from_directories("/tmp/ow-a", "/tmp/ow-b"); + apply_net_overrides(&mut builder); + assert_eq!( + builder.override_net_params().get("cbtmintimeout"), + Some(&CBT_MIN_TIMEOUT_MSEC) + ); + assert!( + CBT_MIN_TIMEOUT_MSEC >= 10_000, + "learned current_timeout was 998ms; HsDir uploads need a multi-second floor" + ); + } +} diff --git a/tests/tor_hs.rs b/tests/tor_hs.rs index 941a8c7..7745295 100644 --- a/tests/tor_hs.rs +++ b/tests/tor_hs.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use std::time::{Duration, Instant}; -use arti_client::config::TorClientConfigBuilder; use arti_client::{DormantMode, TorClient, TorClientConfig}; use futures::StreamExt; use futures::io::{AsyncReadExt, AsyncWriteExt}; @@ -23,12 +22,7 @@ const BOOTSTRAP_LOG: &str = "info"; type Client = Arc>; 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); - // Temp dirs sit under $TMP; skip fs-mistrust on the parent tree. - builder.storage().permissions().dangerously_trust_everyone(); - builder.build().expect("TorClientConfig") + onionwire::hs::client_config(state_dir, cache_dir) } async fn bootstrapped(state_dir: &std::path::Path, cache_dir: &std::path::Path) -> Client { @@ -89,7 +83,7 @@ async fn launch_echo( } async fn wait_until_published(svc: &RunningOnionService, label: &str) { - let deadline = Instant::now() + Duration::from_secs(180); + let deadline = Instant::now() + onionwire::hs::PUBLISH_WAIT; let mut events = svc.status_events(); loop { let st = svc.status(); @@ -103,7 +97,8 @@ async fn wait_until_published(svc: &RunningOnionService, label: &str) { } if Instant::now() >= deadline { panic!( - "{label}: onion service did not publish within 180s: {:?}", + "{label}: onion service did not publish within {}s: {:?}", + onionwire::hs::PUBLISH_WAIT.as_secs(), st.state() ); }