hs: wait 360s for HsDir publish; floor cbtmintimeout at 10s
Some checks failed
ci / test (push) Failing after 6m21s
Some checks failed
ci / test (push) Failing after 6m21s
180s fail-closed cut a working descriptor upload while status was still Bootstrapping. Learned CBT can also drop to ~1s and kill HsDir circuits.
This commit is contained in:
parent
d4fb93c27e
commit
7701f61c71
4 changed files with 44 additions and 68 deletions
|
|
@ -224,7 +224,7 @@ Do not run `cargo publish`; `publish = false`. CI needs the repo secret
|
||||||
|---|---|
|
|---|---|
|
||||||
| `GLIBC_… not found` | Binary is newer than your libc. Build from source (Option B). |
|
| `GLIBC_… not found` | Binary is newer than your libc. Build from source (Option B). |
|
||||||
| `sha256sum: FAILED` | Re-download both the binary and `.sha256`; run the check in the same directory. |
|
| `sha256sum: FAILED` | Re-download both the binary and `.sha256`; run the check in the same directory. |
|
||||||
| Hang on `bootstrapping Arti…` | Need outbound network. First consensus fetch is slow. Wait a couple of minutes; if it never publishes, it fails closed — no C-tor fallback. |
|
| Hang on `bootstrapping Arti…` / `hs status: Bootstrapping` | Outbound network required. Client bootstrap is minutes; HsDir descriptor upload can stay Bootstrapping up to ~6 min before `DegradedReachable`. Fail closed after that — no C-tor fallback. |
|
||||||
| `onionwire: unknown argument` | No subcommands. Flags are `--version` / `--help` only, then the TUI. |
|
| `onionwire: unknown argument` | No subcommands. Flags are `--version` / `--help` only, then the TUI. |
|
||||||
| Blank / broken TUI | Run in a real terminal emulator, not `nohup` / systemd without a TTY. |
|
| Blank / broken TUI | Run in a real terminal emulator, not `nohup` / systemd without a TTY. |
|
||||||
| Two chats, same laptop | Separate `ONIONWIRE_HOME` per process. |
|
| Two chats, same laptop | Separate `ONIONWIRE_HOME` per process. |
|
||||||
|
|
|
||||||
12
src/hs.rs
12
src/hs.rs
|
|
@ -12,6 +12,10 @@ 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;
|
||||||
|
/// 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 type Client = Arc<TorClient<PreferredRuntime>>;
|
||||||
|
|
||||||
|
|
@ -20,6 +24,9 @@ pub fn client_config(state_dir: &std::path::Path, cache_dir: &std::path::Path) -
|
||||||
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();
|
||||||
|
builder
|
||||||
|
.override_net_params()
|
||||||
|
.insert("cbtmintimeout".to_string(), CBT_MIN_TIMEOUT_MS);
|
||||||
builder.build().expect("TorClientConfig")
|
builder.build().expect("TorClientConfig")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,7 +56,7 @@ 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 deadline = Instant::now() + PUBLISH_WAIT;
|
||||||
let mut events = svc.status_events();
|
let mut events = svc.status_events();
|
||||||
loop {
|
loop {
|
||||||
let st = svc.status();
|
let st = svc.status();
|
||||||
|
|
@ -66,7 +73,8 @@ 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: {:?}",
|
||||||
|
PUBLISH_WAIT.as_secs(),
|
||||||
st.state()
|
st.state()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
tests/hs.rs
Normal file
21
tests/hs.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
//! HS publish wait and CBT floor — 180s fail-closed cuts a working HsDir upload.
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use onionwire::hs;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn publish_wait_covers_hsdir_retries() {
|
||||||
|
assert!(
|
||||||
|
hs::PUBLISH_WAIT >= Duration::from_secs(360),
|
||||||
|
"180s cuts a working HsDir publish while status is still Bootstrapping"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cbt_min_timeout_floor_is_at_least_10s() {
|
||||||
|
assert!(
|
||||||
|
hs::CBT_MIN_TIMEOUT_MS >= 10_000,
|
||||||
|
"learned CBT ~1s kills HsDir circuits"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,53 +6,22 @@
|
||||||
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;
|
||||||
use arti_client::{DormantMode, TorClient, TorClientConfig};
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures::io::{AsyncReadExt, AsyncWriteExt};
|
use futures::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use safelog::DisplayRedacted;
|
use onionwire::hs::{self, Client, HS_PORT};
|
||||||
use tor_cell::relaycell::msg::Connected;
|
use tor_cell::relaycell::msg::Connected;
|
||||||
use tor_hsservice::status::State;
|
use tor_hsservice::{RunningOnionService, handle_rend_requests};
|
||||||
use tor_hsservice::{HsNickname, OnionServiceConfig, RunningOnionService, handle_rend_requests};
|
|
||||||
use tor_rtcompat::PreferredRuntime;
|
|
||||||
|
|
||||||
const PING: &[u8] = b"ping";
|
const PING: &[u8] = b"ping";
|
||||||
const HS_PORT: u16 = 80;
|
|
||||||
const BOOTSTRAP_LOG: &str = "info";
|
const BOOTSTRAP_LOG: &str = "info";
|
||||||
|
|
||||||
type Client = Arc<TorClient<PreferredRuntime>>;
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
let cfg = client_config(state_dir, cache_dir);
|
hs::bootstrapped(state_dir, cache_dir)
|
||||||
TorClient::create_bootstrapped(cfg)
|
|
||||||
.await
|
.await
|
||||||
.expect("Arti bootstrap failed — fail closed, no C-tor fallback")
|
.expect("Arti bootstrap failed — fail closed, no C-tor fallback")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hs_config(nickname: &str) -> OnionServiceConfig {
|
|
||||||
let nickname = HsNickname::new(nickname.to_string()).expect("HsNickname");
|
|
||||||
OnionServiceConfig::builder()
|
|
||||||
.nickname(nickname)
|
|
||||||
.build()
|
|
||||||
.expect("OnionServiceConfig")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn onion_string(svc: &RunningOnionService) -> String {
|
|
||||||
let id = svc
|
|
||||||
.onion_address()
|
|
||||||
.expect("onion identity missing from keystore");
|
|
||||||
id.display_unredacted().to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_echo(
|
fn spawn_echo(
|
||||||
rend: impl futures::Stream<Item = tor_hsservice::RendRequest> + Send + 'static,
|
rend: impl futures::Stream<Item = tor_hsservice::RendRequest> + Send + 'static,
|
||||||
) -> tokio::task::JoinHandle<()> {
|
) -> tokio::task::JoinHandle<()> {
|
||||||
|
|
@ -79,41 +48,15 @@ async fn launch_echo(
|
||||||
nickname: &str,
|
nickname: &str,
|
||||||
) -> (Arc<RunningOnionService>, tokio::task::JoinHandle<()>, String) {
|
) -> (Arc<RunningOnionService>, tokio::task::JoinHandle<()>, String) {
|
||||||
let launched = client
|
let launched = client
|
||||||
.launch_onion_service(hs_config(nickname))
|
.launch_onion_service(hs::hs_config(nickname).expect("hs_config"))
|
||||||
.expect("launch_onion_service")
|
.expect("launch_onion_service")
|
||||||
.expect("onion service disabled in config — fail closed");
|
.expect("onion service disabled in config — fail closed");
|
||||||
let (svc, rend) = launched;
|
let (svc, rend) = launched;
|
||||||
let onion = onion_string(&svc);
|
let onion = hs::onion_string(&svc).expect("onion_string");
|
||||||
let echo = spawn_echo(rend);
|
let echo = spawn_echo(rend);
|
||||||
(svc, echo, onion)
|
(svc, echo, onion)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_until_published(svc: &RunningOnionService, label: &str) {
|
|
||||||
let deadline = Instant::now() + Duration::from_secs(180);
|
|
||||||
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,
|
|
||||||
State::Broken => {
|
|
||||||
panic!("{label}: onion service broken: {:?}", st.current_problem())
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
if Instant::now() >= deadline {
|
|
||||||
panic!(
|
|
||||||
"{label}: onion service did not publish within 180s: {:?}",
|
|
||||||
st.state()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
tokio::select! {
|
|
||||||
_ = events.next() => {}
|
|
||||||
_ = tokio::time::sleep(Duration::from_secs(2)) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn echo_ping(client: &Client, onion: &str) -> Result<(), String> {
|
async fn echo_ping(client: &Client, onion: &str) -> Result<(), String> {
|
||||||
let mut stream = client
|
let mut stream = client
|
||||||
.connect((onion, HS_PORT))
|
.connect((onion, HS_PORT))
|
||||||
|
|
@ -181,8 +124,12 @@ async fn two_node_byte_pipe_restart_and_dormant() {
|
||||||
eprintln!("bob onion={bob_onion}");
|
eprintln!("bob onion={bob_onion}");
|
||||||
assert_ne!(alice_onion, bob_onion, "separate HS identities");
|
assert_ne!(alice_onion, bob_onion, "separate HS identities");
|
||||||
|
|
||||||
wait_until_published(&alice_svc, "alice").await;
|
hs::wait_until_published(&alice_svc, "alice")
|
||||||
wait_until_published(&bob_svc, "bob").await;
|
.await
|
||||||
|
.expect("alice publish");
|
||||||
|
hs::wait_until_published(&bob_svc, "bob")
|
||||||
|
.await
|
||||||
|
.expect("bob publish");
|
||||||
|
|
||||||
eprintln!("echo ping alice → bob");
|
eprintln!("echo ping alice → bob");
|
||||||
echo_ping_retry(&alice, &bob_onion, Duration::from_secs(180)).await;
|
echo_ping_retry(&alice, &bob_onion, Duration::from_secs(180)).await;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue