status shows each home relay: connected, down with the error, or denied

iroh 1.3's home_relay_status, on GlobalStatus as relays (absent when
empty, so older clients parse it). The thing we lacked when fetches
timed out: whether this daemon can be reached through a relay at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-10-03 14:08:41 +02:00
co-authored by Claude Fable 5.1
parent 7916e59acb
commit 7322812524
4 changed files with 57 additions and 1 deletions
+9
View File
@@ -272,6 +272,15 @@ fn print_human(data: Option<&ResponseData>) {
println!("store: {}", s.store_path);
println!("pins: {}", s.pins);
println!("peers: {} connected", s.connected_peers);
for r in &s.relays {
let how = match (&r.auth_denied, &r.last_error, r.connected) {
(Some(reason), _, _) => format!("denied: {reason}"),
(None, Some(err), false) => format!("down: {err}"),
(None, None, false) => "connecting".to_string(),
(None, _, true) => "connected".to_string(),
};
println!("relay: {} {how}", r.url);
}
}
ResponseData::HashStatus(h) => {
println!("hash: {}", h.hash);
+1
View File
@@ -486,6 +486,7 @@ impl Daemon {
store_path: self.config.store_dir.display().to_string(),
pins: self.meta.pins().len() as u64,
connected_peers: connected,
relays: self.transfer.relay_status(),
}))
}
Request::Status { hash: Some(hash) } => {
+17 -1
View File
@@ -12,7 +12,7 @@ use std::time::Duration;
use anyhow::{Context, Result};
use iroh::address_lookup::memory::MemoryLookup;
use iroh::protocol::Router;
use iroh::{Endpoint, EndpointAddr, EndpointId, RelayMode, SecretKey};
use iroh::{Endpoint, EndpointAddr, EndpointId, RelayMode, SecretKey, Watcher as _};
use iroh_blobs::api::downloader::{DownloadOptions, Downloader, Shuffled, SplitStrategy};
use iroh_blobs::protocol::GetRequest;
use iroh_blobs::{BlobFormat, BlobsProtocol, Hash, HashAndFormat};
@@ -156,6 +156,22 @@ impl Transfer {
self.endpoint.id().to_string()
}
/// How each home relay is doing right now: connected, or why not.
/// Empty with relays disabled.
pub fn relay_status(&self) -> Vec<varde_proto::RelayInfo> {
self.endpoint
.home_relay_status()
.get()
.iter()
.map(|r| varde_proto::RelayInfo {
url: r.url().to_string(),
connected: r.is_connected(),
last_error: r.last_error().map(|e| format!("{e:#}")),
auth_denied: r.auth_denied_reason().map(str::to_string),
})
.collect()
}
/// The endpoint's identity key (also signs our announcements).
pub fn secret_key(&self) -> SecretKey {
self.endpoint.secret_key().clone()
+30
View File
@@ -324,6 +324,27 @@ pub struct GlobalStatus {
pub pins: u64,
/// Number of connected trusted peers.
pub connected_peers: u64,
/// The home relays this endpoint uses and how each is doing. Empty
/// when relays are off (LAN-only) or on a daemon before 0.1.10.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub relays: Vec<RelayInfo>,
}
/// One home relay's connection state (iroh's `home_relay_status`).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RelayInfo {
/// The relay URL.
pub url: String,
/// Whether the endpoint holds a connection to it right now.
pub connected: bool,
/// The most recent connection error, when not connected and an
/// attempt has failed; for a person to read, not to match on.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_error: Option<String>,
/// The relay's reason when it denied this endpoint's authentication:
/// the one failure retrying will not cure.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auth_denied: Option<String>,
}
/// Status of one hash.
@@ -478,4 +499,13 @@ mod tests {
_ => panic!("wrong variant"),
}
}
#[test]
fn a_status_from_before_relays_still_parses() {
let old = r#"{"version":"0.1.9","protocol":2,"store_path":"/s","pins":1,"connected_peers":0}"#;
let status: GlobalStatus = serde_json::from_str(old).unwrap();
assert!(status.relays.is_empty());
let back = serde_json::to_string(&status).unwrap();
assert!(!back.contains("relays"), "empty stays absent for old clients: {back}");
}
}