diff --git a/iroh-cli/src/commands/doctor.rs b/iroh-cli/src/commands/doctor.rs index 6f98302a52..3b2a8348c6 100644 --- a/iroh-cli/src/commands/doctor.rs +++ b/iroh-cli/src/commands/doctor.rs @@ -30,7 +30,7 @@ use iroh::{ defaults::DEFAULT_STUN_PORT, discovery::{dns::DnsDiscovery, pkarr::PkarrPublisher, ConcurrentDiscovery, Discovery}, dns::default_resolver, - endpoint::{self, Connection, ConnectionTypeStream, RecvStream, SendStream}, + endpoint::{self, Connection, ConnectionTypeStream, RecvStream, RemoteInfo, SendStream}, key::{PublicKey, SecretKey}, netcheck, portmapper, relay::{RelayMap, RelayMode, RelayUrl}, @@ -380,7 +380,7 @@ impl Gui { let mp = MultiProgress::new(); mp.set_draw_target(indicatif::ProgressDrawTarget::stderr()); let counters = mp.add(ProgressBar::hidden()); - let conn_info = mp.add(ProgressBar::hidden()); + let remote_info = mp.add(ProgressBar::hidden()); let send_pb = mp.add(ProgressBar::hidden()); let recv_pb = mp.add(ProgressBar::hidden()); let echo_pb = mp.add(ProgressBar::hidden()); @@ -390,7 +390,7 @@ impl Gui { send_pb.set_style(style.clone()); recv_pb.set_style(style.clone()); echo_pb.set_style(style.clone()); - conn_info.set_style(style.clone()); + remote_info.set_style(style.clone()); counters.set_style(style); let pb = mp.add(indicatif::ProgressBar::hidden()); pb.enable_steady_tick(Duration::from_millis(100)); @@ -401,7 +401,7 @@ impl Gui { let counter_task = tokio::spawn(async move { loop { Self::update_counters(&counters2); - Self::update_connection_info(&conn_info, &endpoint, &node_id); + Self::update_remote_info(&remote_info, &endpoint, &node_id); tokio::time::sleep(Duration::from_millis(100)).await; } }); @@ -419,13 +419,13 @@ impl Gui { } } - fn update_connection_info(target: &ProgressBar, endpoint: &Endpoint, node_id: &NodeId) { + fn update_remote_info(target: &ProgressBar, endpoint: &Endpoint, node_id: &NodeId) { let format_latency = |x: Option| { x.map(|x| format!("{:.6}s", x.as_secs_f64())) .unwrap_or_else(|| "unknown".to_string()) }; - let msg = match endpoint.connection_info(*node_id) { - Some(endpoint::ConnectionInfo { + let msg = match endpoint.remote_info(*node_id) { + Some(RemoteInfo { relay_url, conn_type, latency, diff --git a/iroh-cli/src/commands/node.rs b/iroh-cli/src/commands/node.rs index 5b2eac8e6f..5a57ee43ce 100644 --- a/iroh-cli/src/commands/node.rs +++ b/iroh-cli/src/commands/node.rs @@ -9,17 +9,17 @@ use comfy_table::{presets::NOTHING, Cell}; use futures_lite::{Stream, StreamExt}; use human_time::ToHumanTimeString; use iroh::client::Iroh; -use iroh::net::endpoint::{ConnectionInfo, DirectAddrInfo}; +use iroh::net::endpoint::{DirectAddrInfo, RemoteInfo}; use iroh::net::relay::RelayUrl; use iroh::net::{NodeAddr, NodeId}; #[derive(Subcommand, Debug, Clone)] #[allow(clippy::large_enum_variant)] pub enum NodeCommands { - /// Get information about the different connections we have made - Connections, - /// Get connection information about a particular node - ConnectionInfo { node_id: NodeId }, + /// Get information about the different remote nodes. + RemoteList, + /// Get information about a particular remote node. + Remote { node_id: NodeId }, /// Get status of the running node. Status, /// Get statistics and metrics from the running node. @@ -48,8 +48,8 @@ pub enum NodeCommands { impl NodeCommands { pub async fn run(self, iroh: &Iroh) -> Result<()> { match self { - Self::Connections => { - let connections = iroh.connections().await?; + Self::RemoteList => { + let connections = iroh.remote_infos_iter().await?; let timestamp = time::OffsetDateTime::now_utc() .format(&time::format_description::well_known::Rfc2822) .unwrap_or_else(|_| String::from("failed to get current time")); @@ -58,13 +58,13 @@ impl NodeCommands { " {}: {}\n\n{}", "current time".bold(), timestamp, - fmt_connections(connections).await + fmt_remote_infos(connections).await ); } - Self::ConnectionInfo { node_id } => { - let conn_info = iroh.connection_info(node_id).await?; - match conn_info { - Some(info) => println!("{}", fmt_connection(info)), + Self::Remote { node_id } => { + let info = iroh.remote_info(node_id).await?; + match info { + Some(info) => println!("{}", fmt_info(info)), None => println!("Not Found"), } } @@ -126,8 +126,8 @@ impl NodeCommands { } } -async fn fmt_connections( - mut infos: impl Stream> + Unpin, +async fn fmt_remote_infos( + mut infos: impl Stream> + Unpin, ) -> String { let mut table = Table::new(); table.load_preset(NOTHING).set_header( @@ -135,19 +135,19 @@ async fn fmt_connections( .into_iter() .map(bold_cell), ); - while let Some(Ok(conn_info)) = infos.next().await { - let node_id: Cell = conn_info.node_id.to_string().into(); - let relay_url = conn_info + while let Some(Ok(info)) = infos.next().await { + let node_id: Cell = info.node_id.to_string().into(); + let relay_url = info .relay_url .map_or(String::new(), |url_info| url_info.relay_url.to_string()) .into(); - let conn_type = conn_info.conn_type.to_string().into(); - let latency = match conn_info.latency { + let conn_type = info.conn_type.to_string().into(); + let latency = match info.latency { Some(latency) => latency.to_human_time_string(), None => String::from("unknown"), } .into(); - let last_used = conn_info + let last_used = info .last_used .map(fmt_how_long_ago) .map(Cell::new) @@ -157,9 +157,8 @@ async fn fmt_connections( table.to_string() } -fn fmt_connection(info: ConnectionInfo) -> String { - let ConnectionInfo { - id: _, +fn fmt_info(info: RemoteInfo) -> String { + let RemoteInfo { node_id, relay_url, addrs, diff --git a/iroh-net/src/discovery.rs b/iroh-net/src/discovery.rs index 9964856c46..af45dbd6b7 100644 --- a/iroh-net/src/discovery.rs +++ b/iroh-net/src/discovery.rs @@ -260,11 +260,14 @@ impl DiscoveryTask { /// We need discovery if we have no paths to the node, or if the paths we do have /// have timed out. fn needs_discovery(ep: &Endpoint, node_id: NodeId) -> bool { - match ep.connection_info(node_id) { - // No connection info means no path to node -> start discovery. + match ep.remote_info(node_id) { + // No info means no path to node -> start discovery. None => true, Some(info) => { - match (info.last_received(), info.last_alive_relay()) { + match ( + info.last_received(), + info.relay_url.as_ref().and_then(|r| r.last_alive), + ) { // No path to node -> start discovery. (None, None) => true, // If we haven't received on direct addresses or the relay for MAX_AGE, diff --git a/iroh-net/src/endpoint.rs b/iroh-net/src/endpoint.rs index 9b844467aa..74e0f2e848 100644 --- a/iroh-net/src/endpoint.rs +++ b/iroh-net/src/endpoint.rs @@ -45,8 +45,8 @@ pub use quinn::{ }; pub use super::magicsock::{ - ConnectionInfo, ConnectionType, ConnectionTypeStream, ControlMsg, DirectAddr, DirectAddrInfo, - DirectAddrType, DirectAddrsStream, + ConnectionType, ConnectionTypeStream, ControlMsg, DirectAddr, DirectAddrInfo, DirectAddrType, + DirectAddrsStream, RemoteInfo, }; pub use iroh_base::node_addr::{AddrInfo, NodeAddr}; @@ -708,26 +708,33 @@ impl Endpoint { // # Getter methods for information about other nodes. - /// Returns connection information about a specific node. + /// Returns information about the remote node identified by a [`NodeId`]. /// - /// Then [`Endpoint`] stores some information about all the other iroh-net nodes it has - /// information about. This includes information about the relay server in use, any - /// known direct addresses, when there was last any contact with this node and what kind - /// of connection this was. - pub fn connection_info(&self, node_id: NodeId) -> Option { - self.msock.connection_info(node_id) + /// The [`Endpoint`] keeps some information about remote iroh-net nodes, which it uses to find + /// the best path to a node. Having information on a remote node, however, does not mean we have + /// ever connected to it to or even whether a connection is even possible. The information about a + /// remote node will change over time, as the [`Endpoint`] learns more about the node. Future + /// calls may return different information. Furthermore, node information may even be + /// completely evicted as it becomes stale. + /// + /// See also [`Endpoint::remote_infos_iter`] which returns information on all nodes known + /// by this [`Endpoint`]. + pub fn remote_info(&self, node_id: NodeId) -> Option { + self.msock.remote_info(node_id) } - /// Returns information on all the nodes we have connection information about. + /// Returns information about all the remote nodes this [`Endpoint`] knows about. + /// + /// This returns the same information as [`Endpoint::remote_info`] for each node known to this + /// [`Endpoint`]. /// - /// This returns the same information as [`Endpoint::connection_info`] for each node - /// known to this [`Endpoint`]. + /// The [`Endpoint`] keeps some information about remote iroh-net nodes, which it uses to find + /// the best path to a node. This returns all the nodes it knows about, regardless of whether a + /// connection was ever made or is even possible. /// - /// Connections are currently only pruned on user action when using - /// [`Endpoint::add_node_addr`] so these connections are not necessarily active - /// connections. - pub fn connection_infos(&self) -> Vec { - self.msock.connection_infos() + /// See also [`Endpoint::remote_info`] to only retrieve information about a single node. + pub fn remote_infos_iter(&self) -> impl Iterator { + self.msock.list_remote_infos().into_iter() } // # Methods for less common getters. @@ -1279,15 +1286,11 @@ mod tests { // first time, create a magic endpoint without peers but a peers file and add addressing // information for a peer let endpoint = new_endpoint(secret_key.clone(), None).await; - assert!(endpoint.connection_infos().is_empty()); + assert_eq!(endpoint.remote_infos_iter().count(), 0); endpoint.add_node_addr(node_addr.clone()).unwrap(); // Grab the current addrs - let node_addrs: Vec = endpoint - .connection_infos() - .into_iter() - .map(Into::into) - .collect(); + let node_addrs: Vec = endpoint.remote_infos_iter().map(Into::into).collect(); assert_eq!(node_addrs.len(), 1); assert_eq!(node_addrs[0], node_addr); @@ -1298,7 +1301,7 @@ mod tests { info!("restarting endpoint"); // now restart it and check the addressing info of the peer let endpoint = new_endpoint(secret_key, Some(node_addrs)).await; - let ConnectionInfo { mut addrs, .. } = endpoint.connection_info(peer_id).unwrap(); + let RemoteInfo { mut addrs, .. } = endpoint.remote_info(peer_id).unwrap(); let conn_addr = addrs.pop().unwrap().addr; assert_eq!(conn_addr, direct_addr); } diff --git a/iroh-net/src/magicsock.rs b/iroh-net/src/magicsock.rs index 995ec326f4..a443569ee7 100644 --- a/iroh-net/src/magicsock.rs +++ b/iroh-net/src/magicsock.rs @@ -75,12 +75,14 @@ mod relay_actor; mod timer; mod udp_conn; +pub(crate) use node_map::Source; + +pub(super) use self::timer::Timer; + pub use self::metrics::Metrics; pub use self::node_map::{ - ConnectionType, ConnectionTypeStream, ControlMsg, DirectAddrInfo, NodeInfo as ConnectionInfo, + ConnectionType, ConnectionTypeStream, ControlMsg, DirectAddrInfo, RemoteInfo, }; -pub(super) use self::timer::Timer; -pub(crate) use node_map::Source; /// How long we consider a STUN-derived endpoint valid for. UDP NAT mappings typically /// expire at 30 seconds, so this is a few seconds shy of that. @@ -286,19 +288,19 @@ impl MagicSock { /// Returns `true` if we have at least one candidate address where we can send packets to. pub(crate) fn has_send_address(&self, node_key: PublicKey) -> bool { - self.connection_info(node_key) + self.remote_info(node_key) .map(|info| info.has_send_address()) .unwrap_or(false) } - /// Retrieve connection information about nodes in the network. - pub(crate) fn connection_infos(&self) -> Vec { - self.node_map.node_infos(Instant::now()) + /// Return the [`RemoteInfo`]s of all nodes in the node map. + pub(crate) fn list_remote_infos(&self) -> Vec { + self.node_map.list_remote_infos(Instant::now()) } - /// Retrieve connection information about a node in the network. - pub(crate) fn connection_info(&self, node_id: NodeId) -> Option { - self.node_map.node_info(node_id) + /// Return the [`RemoteInfo`] for a single node in the node map. + pub(crate) fn remote_info(&self, node_id: NodeId) -> Option { + self.node_map.remote_info(node_id) } /// Returns the direct addresses as a stream. @@ -2782,7 +2784,7 @@ mod tests { fn tracked_endpoints(&self) -> Vec { self.endpoint .magic_sock() - .connection_infos() + .list_remote_infos() .into_iter() .map(|ep| ep.node_id) .collect() diff --git a/iroh-net/src/magicsock/node_map.rs b/iroh-net/src/magicsock/node_map.rs index a91510674c..74b7dc1132 100644 --- a/iroh-net/src/magicsock/node_map.rs +++ b/iroh-net/src/magicsock/node_map.rs @@ -33,9 +33,10 @@ mod node_state; mod path_state; mod udp_paths; -pub use node_state::{ConnectionType, ControlMsg, DirectAddrInfo, NodeInfo}; pub(super) use node_state::{DiscoPingPurpose, PingAction, PingRole, SendPing}; +pub use node_state::{ConnectionType, ControlMsg, DirectAddrInfo, RemoteInfo}; + /// Number of nodes that are inactive for which we keep info about. This limit is enforced /// periodically via [`NodeMap::prune_inactive`]. const MAX_INACTIVE_NODES: usize = 30; @@ -224,9 +225,13 @@ impl NodeMap { .collect() } - /// Gets the [`NodeInfo`]s for each endpoint - pub(super) fn node_infos(&self, now: Instant) -> Vec { - self.inner.lock().node_infos(now) + /// Returns the [`RemoteInfo`]s for each node in the node map. + pub(super) fn list_remote_infos(&self, now: Instant) -> Vec { + // NOTE: calls to this method will often call `into_iter` (or similar methods). Note that + // we can't avoid `collect` here since it would hold a lock for an indefinite time. Even if + // we were to find this acceptable, dealing with the lifetimes of the mutex's guard and the + // internal iterator will be a hassle, if possible at all. + self.inner.lock().remote_infos_iter(now).collect() } /// Returns a stream of [`ConnectionType`]. @@ -242,9 +247,9 @@ impl NodeMap { self.inner.lock().conn_type_stream(node_id) } - /// Get the [`NodeInfo`]s for each endpoint - pub(super) fn node_info(&self, node_id: NodeId) -> Option { - self.inner.lock().node_info(node_id) + /// Get the [`RemoteInfo`]s for the node identified by [`NodeId`]. + pub(super) fn remote_info(&self, node_id: NodeId) -> Option { + self.inner.lock().remote_info(node_id) } /// Prunes nodes without recent activity so that at most [`MAX_INACTIVE_NODES`] are kept. @@ -385,13 +390,13 @@ impl NodeMapInner { self.by_id.iter_mut() } - /// Get the [`NodeInfo`]s for each endpoint - fn node_infos(&self, now: Instant) -> Vec { - self.node_states().map(|(_, ep)| ep.info(now)).collect() + /// Get the [`RemoteInfo`]s for all nodes. + fn remote_infos_iter(&self, now: Instant) -> impl Iterator + '_ { + self.node_states().map(move |(_, ep)| ep.info(now)) } - /// Get the [`NodeInfo`]s for each endpoint - fn node_info(&self, node_id: NodeId) -> Option { + /// Get the [`RemoteInfo`]s for each node. + fn remote_info(&self, node_id: NodeId) -> Option { self.get(NodeStateKey::NodeId(node_id)) .map(|ep| ep.info(Instant::now())) } @@ -666,7 +671,7 @@ mod tests { node_map.add_test_addr(node_addr_d); let mut addrs: Vec = node_map - .node_infos(Instant::now()) + .list_remote_infos(Instant::now()) .into_iter() .filter_map(|info| { let addr: NodeAddr = info.into(); @@ -679,7 +684,7 @@ mod tests { let loaded_node_map = NodeMap::load_from_vec(addrs.clone()); let mut loaded: Vec = loaded_node_map - .node_infos(Instant::now()) + .list_remote_infos(Instant::now()) .into_iter() .filter_map(|info| { let addr: NodeAddr = info.into(); diff --git a/iroh-net/src/magicsock/node_map/node_state.rs b/iroh-net/src/magicsock/node_map/node_state.rs index c387b97647..b5e1af2a1e 100644 --- a/iroh-net/src/magicsock/node_map/node_state.rs +++ b/iroh-net/src/magicsock/node_map/node_state.rs @@ -111,8 +111,11 @@ pub(super) struct NodeState { sent_pings: HashMap, /// Last time this node was used. /// - /// A node is marked as in use when an endpoint to contact them is requested or if UDP activity - /// is registered. + /// A node is marked as in use when sending datagrams to them, or when having received + /// datagrams from it. Regardless of whether the datagrams are payload or DISCO, and whether + /// they go via UDP or the relay. + /// + /// Note that sending datagrams to a node does not mean the node receives them. last_used: Option, /// Last time we sent a call-me-maybe. /// @@ -185,8 +188,8 @@ impl NodeState { self.conn_type.watch().into_stream() } - /// Returns info about this endpoint - pub(super) fn info(&self, now: Instant) -> NodeInfo { + /// Returns info about this node. + pub(super) fn info(&self, now: Instant) -> RemoteInfo { let conn_type = self.conn_type.get(); let latency = match conn_type { ConnectionType::Direct(addr) => self @@ -214,6 +217,7 @@ impl NodeState { } ConnectionType::None => None, }; + let addrs = self .udp_paths .paths @@ -232,8 +236,7 @@ impl NodeState { }) .collect(); - NodeInfo { - id: self.id, + RemoteInfo { node_id: self.node_id, relay_url: self.relay_url.clone().map(|r| r.into()), addrs, @@ -969,7 +972,7 @@ impl NodeState { self.send_pings(now) } - /// Marks this endpoint as having received a UDP payload message. + /// Marks this node as having received a UDP payload message. pub(super) fn receive_udp(&mut self, addr: IpPort, now: Instant) { let Some(state) = self.udp_paths.paths.get_mut(&addr) else { debug_assert!(false, "node map inconsistency by_ip_port <-> direct addr"); @@ -1109,8 +1112,8 @@ impl NodeState { } } -impl From for NodeAddr { - fn from(info: NodeInfo) -> Self { +impl From for NodeAddr { + fn from(info: RemoteInfo) -> Self { let direct_addresses = info .addrs .into_iter() @@ -1185,29 +1188,58 @@ pub enum ControlMsg { CallMeMaybe, } -/// Information about a direct address. +/// Information about a *direct address*. +/// +/// The *direct addresses* of an iroh-net node are those that could be used by other nodes to +/// establish direct connectivity, depending on the network situation. Due to NAT configurations, +/// for example, not all direct addresses of a node are usable by all peers. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct DirectAddrInfo { - /// The address reported. + /// The UDP address reported by the remote node. pub addr: SocketAddr, - /// The latency to the address, if any. + /// The latency to the remote node over this network path. + /// + /// If there has never been any connectivity via this address no latency will be known. pub latency: Option, - /// Last control message received by this node. + /// Last control message received by this node about this address. + /// + /// This contains the elapsed duration since the control message was received and the + /// kind of control message received at that time. Only the most recent control message + /// is returned. + /// + /// Note that [`ControlMsg::CallMeMaybe`] is received via a relay path, while + /// [`ControlMsg::Ping`] and [`ControlMsg::Pong`] are received on the path to + /// [`DirectAddrInfo::addr`] itself and thus convey very different information. pub last_control: Option<(Duration, ControlMsg)>, - /// How long ago was the last payload message for this node. + /// Elapsed time since the last payload message was received on this network path. + /// + /// This indicates how long ago a QUIC datagram was received from the remote node sent + /// from this [`DirectAddrInfo::addr`]. It indicates the network path was in use to + /// transport payload data. pub last_payload: Option, - /// When was this connection last alive, if ever. + /// Elapsed time since this network path was known to exist. + /// + /// A network path be considered to exist only because the remote node advertised it. + /// It may not mean the path is usable. However if there was any communication with the + /// remote node over this network path it also means the path exists. + /// + /// The elapsed time since *any* confirmation of the path's existence was received is + /// returned. If the remote node moved networks and no longer has this path, this could + /// be a long duration. If the path was added via [`Endpoint::add_node_addr`] or some + /// node discovery the path may never have been known to exist. + /// + /// [`Endpoint::add_node_addr`]: crate::endpoint::Endpoint::add_node_addr pub last_alive: Option, } -/// Information about a relay URL. +/// Information about the network path to a remote node via a relay server. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct RelayUrlInfo { - /// The relay url + /// The relay URL. pub relay_url: RelayUrl, - /// How long ago was the relay url last used. + /// Elapsed time since this relay path last received payload or control data. pub last_alive: Option, - /// Latency of the relay url. + /// Latency to the remote node over this relayed network path. pub latency: Option, } @@ -1227,27 +1259,38 @@ impl From for RelayUrl { } } -/// Details about an iroh node which is known to this node. +/// Details about a remote iroh-net node which is known to this node. +/// +/// Having details of a node does not mean it can be connected to, nor that it has ever been +/// connected to in the past. There are various reasons a node might be known: it could have been +/// manually added via [`Endpoint::add_node_addr`], it could have been added by some discovery +/// mechanism, or the node could have contacted this node. +/// +/// [`Endpoint::add_node_addr`]: crate::endpoint::Endpoint::add_node_addr #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct NodeInfo { - /// The id in the node_map - pub id: usize, - /// The public key of the endpoint. +pub struct RemoteInfo { + /// The globally unique identifier for this node. pub node_id: NodeId, - /// relay server information, if available. + /// Relay server information, if available. pub relay_url: Option, - /// List of addresses at which this node might be reachable, plus any latency information we - /// have about that address and the last time the address was used. + /// The addresses at which this node might be reachable. + /// + /// Some of these addresses might only be valid for networks we are not part of, but the remote + /// node might be a part of. pub addrs: Vec, /// The type of connection we have to the node, either direct or over relay. pub conn_type: ConnectionType, - /// The latency of the `conn_type`. + /// The latency of the current network path to the remote node. pub latency: Option, - /// Duration since the last time this node was used. + /// Time elapsed time since last we have sent to or received from the node. + /// + /// This is the duration since *any* data (payload or control messages) was sent or receive + /// from the remote node. Note that sending to the remote node does not imply + /// the remote node received anything. pub last_used: Option, } -impl NodeInfo { +impl RemoteInfo { /// Get the duration since the last activity we received from this endpoint /// on any of its direct addresses. pub fn last_received(&self) -> Option { @@ -1257,13 +1300,19 @@ impl NodeInfo { .min() } - /// Get the duration since the last activity we received from this endpoint - /// on the relay url. + /// Returns the elapsed time since the relay path to the node last received data. + #[deprecated( + since = "0.23.0", + note = "access relay_url.last_alive directly instead" + )] pub fn last_alive_relay(&self) -> Option { self.relay_url.as_ref().and_then(|r| r.last_alive) } - /// Returns `true` if this info contains either a relay URL or at least one direct address. + /// Whether there is a possible known network path to the remote node. + /// + /// Note that this does not provide any guarantees of whether this network path is + /// usable. pub fn has_send_address(&self) -> bool { self.relay_url.is_some() || !self.addrs.is_empty() } @@ -1445,9 +1494,9 @@ mod tests { socket_addr, ) }; - let expect = Vec::from([ - NodeInfo { - id: a_endpoint.id, + + let mut expect = Vec::from([ + RemoteInfo { node_id: a_endpoint.node_id, relay_url: None, addrs: Vec::from([DirectAddrInfo { @@ -1461,8 +1510,7 @@ mod tests { latency: Some(latency), last_used: Some(elapsed), }, - NodeInfo { - id: b_endpoint.id, + RemoteInfo { node_id: b_endpoint.node_id, relay_url: Some(RelayUrlInfo { relay_url: b_endpoint.relay_url.as_ref().unwrap().0.clone(), @@ -1474,8 +1522,7 @@ mod tests { latency: Some(latency), last_used: Some(elapsed), }, - NodeInfo { - id: c_endpoint.id, + RemoteInfo { node_id: c_endpoint.node_id, relay_url: Some(RelayUrlInfo { relay_url: c_endpoint.relay_url.as_ref().unwrap().0.clone(), @@ -1487,8 +1534,7 @@ mod tests { latency: None, last_used: Some(elapsed), }, - NodeInfo { - id: d_endpoint.id, + RemoteInfo { node_id: d_endpoint.node_id, relay_url: Some(RelayUrlInfo { relay_url: d_endpoint.relay_url.as_ref().unwrap().0.clone(), @@ -1533,13 +1579,14 @@ mod tests { ]), next_id: 5, }); - let mut got = node_map.node_infos(later); - got.sort_by_key(|p| p.id); + let mut got = node_map.list_remote_infos(later); + got.sort_by_key(|p| p.node_id); + expect.sort_by_key(|p| p.node_id); remove_non_deterministic_fields(&mut got); assert_eq!(expect, got); } - fn remove_non_deterministic_fields(infos: &mut [NodeInfo]) { + fn remove_non_deterministic_fields(infos: &mut [RemoteInfo]) { for info in infos.iter_mut() { if info.relay_url.is_some() { info.relay_url.as_mut().unwrap().last_alive = None; diff --git a/iroh-net/src/magicsock/node_map/path_state.rs b/iroh-net/src/magicsock/node_map/path_state.rs index 6121d8242d..99483e741a 100644 --- a/iroh-net/src/magicsock/node_map/path_state.rs +++ b/iroh-net/src/magicsock/node_map/path_state.rs @@ -11,8 +11,8 @@ use crate::disco::SendAddr; use crate::magicsock::HEARTBEAT_INTERVAL; use crate::stun; -use super::node_state::{PongReply, SESSION_ACTIVE_TIMEOUT}; -use super::{ControlMsg, IpPort, PingRole}; +use super::node_state::{ControlMsg, PongReply, SESSION_ACTIVE_TIMEOUT}; +use super::{IpPort, PingRole}; /// The minimum time between pings to an endpoint. /// @@ -39,12 +39,14 @@ pub(super) struct PathState { // NOTE: tx_id Originally added in tailscale due to . last_got_ping: Option<(Instant, stun::TransactionId)>, - /// If non-zero, is the time this endpoint was advertised last via a call-me-maybe disco message. + /// The time this endpoint was last advertised via a call-me-maybe DISCO message. pub(super) call_me_maybe_time: Option, /// Last [`PongReply`] received. pub(super) recent_pong: Option, - /// When was this endpoint last used to transmit payload data (removing ping, pong, etc). + /// When the last payload data was **received** via this path. + /// + /// This excludes DISCO messages. pub(super) last_payload_msg: Option, } @@ -160,6 +162,14 @@ impl PathState { .copied() } + /// The last control or DISCO message **about** this path. + /// + /// This is the most recent instant among: + /// - when last pong was received. + /// - when this path was last advertised in a received CallMeMaybe message. + /// - when the last ping from them was received. + /// + /// Returns the time elapsed since the last control message, and the type of control message. pub(super) fn last_control_msg(&self, now: Instant) -> Option<(Duration, ControlMsg)> { // get every control message and assign it its kind let last_pong = self diff --git a/iroh/src/client/node.rs b/iroh/src/client/node.rs index c12c5bd5bb..69b5f44c65 100644 --- a/iroh/src/client/node.rs +++ b/iroh/src/client/node.rs @@ -18,13 +18,13 @@ use std::{collections::BTreeMap, net::SocketAddr}; use anyhow::Result; use futures_lite::{Stream, StreamExt}; -use iroh_net::{endpoint::ConnectionInfo, relay::RelayUrl, NodeAddr, NodeId}; +use iroh_net::{endpoint::RemoteInfo, relay::RelayUrl, NodeAddr, NodeId}; use ref_cast::RefCast; use serde::{Deserialize, Serialize}; use crate::rpc_protocol::node::{ - AddAddrRequest, AddrRequest, ConnectionInfoRequest, ConnectionInfoResponse, ConnectionsRequest, - CounterStats, IdRequest, RelayRequest, ShutdownRequest, StatsRequest, StatusRequest, + AddAddrRequest, AddrRequest, CounterStats, IdRequest, RelayRequest, RemoteInfoRequest, + RemoteInfoResponse, RemoteInfosIterRequest, ShutdownRequest, StatsRequest, StatusRequest, }; use super::{flatten, RpcClient}; @@ -122,24 +122,23 @@ impl Client { Ok(res.stats) } - /// Fetches information about currently known connections. + /// Fetches information about currently known remote nodes. /// /// This streams a *current snapshot*. It does not keep the stream open after finishing /// transferring the snapshot. /// - /// See also [`Endpoint::connection_infos`](crate::net::Endpoint::connection_infos). - pub async fn connections(&self) -> Result>> { - let stream = self.rpc.server_streaming(ConnectionsRequest {}).await?; - Ok(flatten(stream).map(|res| res.map(|res| res.conn_info))) + /// See also [`Endpoint::remote_infos_iter`](crate::net::Endpoint::remote_infos_iter). + pub async fn remote_infos_iter(&self) -> Result>> { + let stream = self.rpc.server_streaming(RemoteInfosIterRequest {}).await?; + Ok(flatten(stream).map(|res| res.map(|res| res.info))) } - /// Fetches connection information about a connection to another node identified by its [`NodeId`]. + /// Fetches node information about a remote iroh node identified by its [`NodeId`]. /// - /// See also [`Endpoint::connection_info`](crate::net::Endpoint::connection_info). - pub async fn connection_info(&self, node_id: NodeId) -> Result> { - let ConnectionInfoResponse { conn_info } = - self.rpc.rpc(ConnectionInfoRequest { node_id }).await??; - Ok(conn_info) + /// See also [`Endpoint::remote_info`](crate::net::Endpoint::remote_info). + pub async fn remote_info(&self, node_id: NodeId) -> Result> { + let RemoteInfoResponse { info } = self.rpc.rpc(RemoteInfoRequest { node_id }).await??; + Ok(info) } /// Fetches status information about this node. diff --git a/iroh/src/node.rs b/iroh/src/node.rs index e0923939dd..3665cf2ee0 100644 --- a/iroh/src/node.rs +++ b/iroh/src/node.rs @@ -49,11 +49,9 @@ use iroh_blobs::util::local_pool::{LocalPool, LocalPoolHandle}; use iroh_blobs::{downloader::Downloader, protocol::Closed}; use iroh_blobs::{HashAndFormat, TempTag}; use iroh_gossip::net::Gossip; +use iroh_net::endpoint::{DirectAddrsStream, RemoteInfo}; use iroh_net::key::SecretKey; -use iroh_net::{ - endpoint::{ConnectionInfo, DirectAddrsStream}, - util::SharedAbortingJoinHandle, -}; +use iroh_net::util::SharedAbortingJoinHandle; use iroh_net::{AddrInfo, Endpoint, NodeAddr}; use quic_rpc::transport::ServerEndpoint as _; use quic_rpc::RpcServer; @@ -620,8 +618,7 @@ async fn handle_connection( } fn node_addresses_for_storage(ep: &Endpoint) -> Vec { - ep.connection_infos() - .into_iter() + ep.remote_infos_iter() .filter_map(node_address_for_storage) .collect() } @@ -631,7 +628,7 @@ fn node_addresses_for_storage(ep: &Endpoint) -> Vec { /// If the endpoint was used, only the paths that were in use will be returned. /// /// Returns `None` if the resulting [`NodeAddr`] would be empty. -fn node_address_for_storage(info: ConnectionInfo) -> Option { +fn node_address_for_storage(info: RemoteInfo) -> Option { let direct_addresses = if info.last_used.is_none() { info.addrs .into_iter() diff --git a/iroh/src/node/rpc.rs b/iroh/src/node/rpc.rs index e6df2bf71c..66bac8e99a 100644 --- a/iroh/src/node/rpc.rs +++ b/iroh/src/node/rpc.rs @@ -64,9 +64,9 @@ use crate::rpc_protocol::{ }, gossip, node, node::{ - AddAddrRequest, AddrRequest, ConnectionInfoRequest, ConnectionInfoResponse, - ConnectionsRequest, ConnectionsResponse, IdRequest, NodeWatchRequest, RelayRequest, - ShutdownRequest, StatsRequest, StatsResponse, StatusRequest, WatchResponse, + AddAddrRequest, AddrRequest, IdRequest, NodeWatchRequest, RelayRequest, RemoteInfoRequest, + RemoteInfoResponse, RemoteInfosIterRequest, RemoteInfosIterResponse, ShutdownRequest, + StatsRequest, StatsResponse, StatusRequest, WatchResponse, }, tags, tags::{DeleteRequest as TagDeleteRequest, ListRequest as ListTagsRequest}, @@ -159,11 +159,11 @@ impl Handler { Relay(msg) => chan.rpc(msg, self, Self::node_relay).await, Shutdown(msg) => chan.rpc(msg, self, Self::node_shutdown).await, Stats(msg) => chan.rpc(msg, self, Self::node_stats).await, - Connections(msg) => { - chan.server_streaming(msg, self, Self::node_connections) + RemoteInfosIter(msg) => { + chan.server_streaming(msg, self, Self::remote_infos_iter) .await } - ConnectionInfo(msg) => chan.rpc(msg, self, Self::node_connection_info).await, + RemoteInfo(msg) => chan.rpc(msg, self, Self::remote_info).await, AddAddr(msg) => chan.rpc(msg, self, Self::node_add_addr).await, } } @@ -1270,17 +1270,17 @@ impl Handler { .into_stream() } - fn node_connections( + fn remote_infos_iter( self, - _: ConnectionsRequest, - ) -> impl Stream> + Send + 'static { + _: RemoteInfosIterRequest, + ) -> impl Stream> + Send + 'static { // provide a little buffer so that we don't slow down the sender let (tx, rx) = async_channel::bounded(32); - let mut conn_infos = self.inner.endpoint.connection_infos(); - conn_infos.sort_by_key(|n| n.node_id.to_string()); + let mut infos: Vec<_> = self.inner.endpoint.remote_infos_iter().collect(); + infos.sort_by_key(|n| n.node_id.to_string()); self.local_pool_handle().spawn_detached(|| async move { - for conn_info in conn_infos { - tx.send(Ok(ConnectionsResponse { conn_info })).await.ok(); + for info in infos { + tx.send(Ok(RemoteInfosIterResponse { info })).await.ok(); } }); rx @@ -1288,13 +1288,10 @@ impl Handler { // This method is called as an RPC method, which have to be async #[allow(clippy::unused_async)] - async fn node_connection_info( - self, - req: ConnectionInfoRequest, - ) -> RpcResult { - let ConnectionInfoRequest { node_id } = req; - let conn_info = self.inner.endpoint.connection_info(node_id); - Ok(ConnectionInfoResponse { conn_info }) + async fn remote_info(self, req: RemoteInfoRequest) -> RpcResult { + let RemoteInfoRequest { node_id } = req; + let info = self.inner.endpoint.remote_info(node_id); + Ok(RemoteInfoResponse { info }) } // This method is called as an RPC method, which have to be async diff --git a/iroh/src/rpc_protocol/node.rs b/iroh/src/rpc_protocol/node.rs index 8b61dbaed3..cfd6d44233 100644 --- a/iroh/src/rpc_protocol/node.rs +++ b/iroh/src/rpc_protocol/node.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use iroh_base::rpc::RpcResult; -use iroh_net::{endpoint::ConnectionInfo, key::PublicKey, relay::RelayUrl, NodeAddr, NodeId}; +use iroh_net::{endpoint::RemoteInfo, key::PublicKey, relay::RelayUrl, NodeAddr, NodeId}; use nested_enum_utils::enum_conversions; use quic_rpc_derive::rpc_requests; use serde::{Deserialize, Serialize}; @@ -29,10 +29,10 @@ pub enum Request { Stats(StatsRequest), #[rpc(response = ())] Shutdown(ShutdownRequest), - #[server_streaming(response = RpcResult)] - Connections(ConnectionsRequest), - #[rpc(response = RpcResult)] - ConnectionInfo(ConnectionInfoRequest), + #[server_streaming(response = RpcResult)] + RemoteInfosIter(RemoteInfosIterRequest), + #[rpc(response = RpcResult)] + RemoteInfo(RemoteInfoRequest), #[server_streaming(response = WatchResponse)] Watch(NodeWatchRequest), } @@ -46,38 +46,39 @@ pub enum Response { Addr(RpcResult), Relay(RpcResult>), Stats(RpcResult), - Connections(RpcResult), - ConnectionInfo(RpcResult), + RemoteInfosIter(RpcResult), + RemoteInfo(RpcResult), Shutdown(()), Watch(WatchResponse), } -/// List connection information about all the nodes we know about +/// List network path information about all the remote nodes known by this node. /// -/// These can be nodes that we have explicitly connected to or nodes -/// that have initiated connections to us. +/// There may never have been connections to these nodes, and connections may not even be +/// possible. Nodes can also become known due to discovery mechanisms +/// or be added manually. #[derive(Debug, Serialize, Deserialize)] -pub struct ConnectionsRequest; +pub struct RemoteInfosIterRequest; -/// A response to a connections request +/// A response to a [`Request::RemoteInfosIter`]. #[derive(Debug, Serialize, Deserialize)] -pub struct ConnectionsResponse { - /// Information about a connection - pub conn_info: ConnectionInfo, +pub struct RemoteInfosIterResponse { + /// Information about a node. + pub info: RemoteInfo, } -/// Get connection information about a specific node +/// Get information about a specific remote node. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConnectionInfoRequest { +pub struct RemoteInfoRequest { /// The node identifier pub node_id: PublicKey, } -/// A response to a connection request +/// A response to a [`Request::RemoteInfo`] request #[derive(Debug, Serialize, Deserialize)] -pub struct ConnectionInfoResponse { - /// Information about a connection to a node - pub conn_info: Option, +pub struct RemoteInfoResponse { + /// Information about a node + pub info: Option, } /// A request to shutdown the node