diff --git a/varde-ctl/src/main.rs b/varde-ctl/src/main.rs index cbc5f3e..9fbf698 100644 --- a/varde-ctl/src/main.rs +++ b/varde-ctl/src/main.rs @@ -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); diff --git a/varde-daemon/src/daemon.rs b/varde-daemon/src/daemon.rs index 33507fa..e31bf79 100644 --- a/varde-daemon/src/daemon.rs +++ b/varde-daemon/src/daemon.rs @@ -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) } => { diff --git a/varde-daemon/src/transfer.rs b/varde-daemon/src/transfer.rs index 57465ba..ffd816c 100644 --- a/varde-daemon/src/transfer.rs +++ b/varde-daemon/src/transfer.rs @@ -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 { + 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() diff --git a/varde-proto/src/lib.rs b/varde-proto/src/lib.rs index 9f6df55..2755570 100644 --- a/varde-proto/src/lib.rs +++ b/varde-proto/src/lib.rs @@ -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, +} + +/// 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, + /// 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, } /// 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}"); + } }