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
This commit is contained in:
Sirius DevOps 2026-09-10 13:30:05 -04:00
parent a278e71627
commit dcd458ab53
No known key found for this signature in database
2 changed files with 59 additions and 13 deletions

View file

@ -12,14 +12,30 @@ use tor_hsservice::{HsNickname, OnionServiceConfig, RunningOnionService};
use tor_rtcompat::PreferredRuntime; use tor_rtcompat::PreferredRuntime;
pub const HS_PORT: u16 = 80; 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<TorClient<PreferredRuntime>>; pub type Client = Arc<TorClient<PreferredRuntime>>;
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 { 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(state_dir).expect("state dir");
std::fs::create_dir_all(cache_dir).expect("cache dir"); std::fs::create_dir_all(cache_dir).expect("cache dir");
let mut builder = TorClientConfigBuilder::from_directories(state_dir, cache_dir); let mut builder = TorClientConfigBuilder::from_directories(state_dir, cache_dir);
builder.storage().permissions().dangerously_trust_everyone(); builder.storage().permissions().dangerously_trust_everyone();
apply_net_overrides(&mut builder);
builder.build().expect("TorClientConfig") builder.build().expect("TorClientConfig")
} }
@ -49,11 +65,17 @@ pub fn onion_string(svc: &RunningOnionService) -> Result<String, String> {
} }
pub async fn wait_until_published(svc: &RunningOnionService, label: &str) -> Result<(), String> { 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(); let mut events = svc.status_events();
loop { loop {
let st = svc.status(); 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() { match st.state() {
State::Running | State::DegradedReachable => return Ok(()), State::Running | State::DegradedReachable => return Ok(()),
State::Broken => { State::Broken => {
@ -66,8 +88,10 @@ pub async fn wait_until_published(svc: &RunningOnionService, label: &str) -> Res
} }
if Instant::now() >= deadline { if Instant::now() >= deadline {
return Err(format!( return Err(format!(
"{label}: onion service did not publish within 180s: {:?}", "{label}: onion service did not publish within {}s: {:?} problem={:?}",
st.state() PUBLISH_WAIT.as_secs(),
st.state(),
st.current_problem()
)); ));
} }
tokio::select! { 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"
);
}
}

View file

@ -6,7 +6,6 @@
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use arti_client::config::TorClientConfigBuilder;
use arti_client::{DormantMode, TorClient, TorClientConfig}; use arti_client::{DormantMode, TorClient, TorClientConfig};
use futures::StreamExt; use futures::StreamExt;
use futures::io::{AsyncReadExt, AsyncWriteExt}; use futures::io::{AsyncReadExt, AsyncWriteExt};
@ -23,12 +22,7 @@ const BOOTSTRAP_LOG: &str = "info";
type Client = Arc<TorClient<PreferredRuntime>>; type Client = Arc<TorClient<PreferredRuntime>>;
fn client_config(state_dir: &std::path::Path, cache_dir: &std::path::Path) -> TorClientConfig { fn client_config(state_dir: &std::path::Path, cache_dir: &std::path::Path) -> TorClientConfig {
std::fs::create_dir_all(state_dir).expect("state dir"); onionwire::hs::client_config(state_dir, cache_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")
} }
async fn bootstrapped(state_dir: &std::path::Path, cache_dir: &std::path::Path) -> Client { 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) { 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(); let mut events = svc.status_events();
loop { loop {
let st = svc.status(); let st = svc.status();
@ -103,7 +97,8 @@ async fn wait_until_published(svc: &RunningOnionService, label: &str) {
} }
if Instant::now() >= deadline { if Instant::now() >= deadline {
panic!( 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() st.state()
); );
} }