diff options
| -rw-r--r-- | docs/openapi.yaml | 6 | ||||
| -rw-r--r-- | src/transport/libp2p.rs | 39 | ||||
| -rw-r--r-- | src/transport/mod.rs | 7 | ||||
| -rw-r--r-- | src/transport/paths.rs | 44 | ||||
| -rw-r--r-- | src/transport/stats.rs | 7 | ||||
| -rw-r--r-- | src/transport/tor.rs | 5 |
6 files changed, 88 insertions, 20 deletions
diff --git a/docs/openapi.yaml b/docs/openapi.yaml index bb3e176..34d878b 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1153,6 +1153,10 @@ components: properties: address: type: string + description: '`<transport>:<transport identity>`: a libp2p peer ID, a Mycelium public key, a node key for Tor.' + location: + type: [string, 'null'] + description: Where the path goes now. libp2p, the remote address of its connection; Tor, the onion service. Null for Mycelium (the address is where it goes), or with no connection. rttMs: type: [number, 'null'] description: Smoothed round-trip time of its PINGs (the keepalive's, and the paths layer's own); null before an answer. @@ -1457,6 +1461,8 @@ components: properties: address: type: string + location: + type: [string, 'null'] rttMs: type: [number, 'null'] down: diff --git a/src/transport/libp2p.rs b/src/transport/libp2p.rs index 76d07ff..237fd8a 100644 --- a/src/transport/libp2p.rs +++ b/src/transport/libp2p.rs @@ -201,8 +201,8 @@ struct Book { failed_relays: HashMap<PeerId, tokio::time::Instant>, /// Relays that granted us a slot we hold. slots: std::collections::HashSet<PeerId>, - /// Open connections: to whom, and whether through a relay. - connections: HashMap<ConnectionId, (PeerId, bool)>, + /// Open connections: to whom, whether through a relay, and the remote address. + connections: HashMap<ConnectionId, (PeerId, bool, Multiaddr)>, /// What AutoNAT says, and the address it confirmed while public. reachability: Reachability, public_address: Option<Multiaddr>, @@ -245,7 +245,7 @@ impl Book { /// How we are connected to `peer`: directly if any connection is direct. fn connection(&self, peer: &PeerId) -> Option<Connection> { let mut found = None; - for (connected, relayed) in self.connections.values() { + for (connected, relayed, _) in self.connections.values() { if connected == peer { if !relayed { return Some(Connection::Direct); @@ -256,9 +256,23 @@ impl Book { found } + /// The remote address of a connection to `peer`: a direct one if there is one. + fn location(&self, peer: &PeerId) -> Option<&Multiaddr> { + let mut found = None; + for (connected, relayed, address) in self.connections.values() { + if connected == peer { + if !relayed { + return Some(address); + } + found = Some(address); + } + } + found + } + fn stats(&self) -> Libp2pStats { let mut connections = Connections::default(); - for (_, relayed) in self.connections.values() { + for (_, relayed, _) in self.connections.values() { if *relayed { connections.relayed += 1; } else { @@ -966,7 +980,10 @@ async fn run( let mut book = lock(&book); book.touch(peer_id); let relayed = endpoint.is_relayed(); - book.connections.insert(connection_id, (peer_id, relayed)); + book.connections.insert( + connection_id, + (peer_id, relayed, endpoint.get_remote_address().clone()), + ); debug!( peer = %peer_id, connection = %connection_id, @@ -1297,6 +1314,11 @@ impl Transport for Libp2pTransport { lock(&self.book).connection(&peer) } + fn location(&self, target: &str) -> Option<String> { + let peer = PeerId::from_str(target).ok()?; + lock(&self.book).location(&peer).map(Multiaddr::to_string) + } + fn stats(&self) -> TransportStats { TransportStats { libp2p: Some(lock(&self.book).stats()), @@ -1826,8 +1848,9 @@ mod tests { let mut id = 0; let mut connect = |book: &mut Book, peer: PeerId, relayed: bool| { id += 1; + let address: Multiaddr = format!("/ip4/10.0.0.{id}/tcp/1").parse().unwrap(); book.connections - .insert(ConnectionId::new_unchecked(id), (peer, relayed)); + .insert(ConnectionId::new_unchecked(id), (peer, relayed, address)); }; connect(&mut book, relayed_only, true); connect(&mut book, both, true); @@ -1835,6 +1858,10 @@ mod tests { assert_eq!(book.connection(&relayed_only), Some(Connection::Relayed)); assert_eq!(book.connection(&both), Some(Connection::Direct)); assert_eq!(book.connection(&peer()), None); + assert_eq!( + book.location(&both).map(|address| address.to_string()), + Some("/ip4/10.0.0.3/tcp/1".to_owned()) + ); let connections = book.stats().connections; assert_eq!((connections.direct, connections.relayed), (1, 2)); } diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 7c31dc6..c70d27f 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -669,6 +669,13 @@ pub(crate) trait Transport: Send + Sync { None } + /// Where messages to `target` (a transport identity) go now: the remote address of + /// the connection in use, say. By default nowhere but `target` itself (Mycelium). + fn location(&self, target: &str) -> Option<String> { + let _ = target; + None + } + /// How this node is connected to `target` (a transport identity), if it is. By /// default not at all, for a transport without connections (Mycelium). fn connection(&self, target: &str) -> Option<Connection> { diff --git a/src/transport/paths.rs b/src/transport/paths.rs index 6696e1a..1f6d078 100644 --- a/src/transport/paths.rs +++ b/src/transport/paths.rs @@ -334,6 +334,7 @@ impl Table { let path = self.paths.get(address); PathInfo { address: address.to_owned(), + location: None, rtt_ms: path .and_then(|path| path.rtt) .map(|rtt| rtt.as_secs_f64() * 1000.0), @@ -439,11 +440,25 @@ impl Paths { /// What paths knows about each of `addresses`. pub(crate) fn info(&self, addresses: &[String]) -> Vec<PathInfo> { let now = Instant::now(); - let table = self.table.lock_or_recover(); - addresses - .iter() - .map(|address| table.info(address, now)) - .collect() + let infos = { + let table = self.table.lock_or_recover(); + addresses + .iter() + .map(|address| table.info(address, now)) + .collect() + }; + self.located(infos) + } + + /// `infos` with where each path goes (`Transport::location`); asked outside the + /// table's lock. + fn located(&self, mut infos: Vec<PathInfo>) -> Vec<PathInfo> { + for info in &mut infos { + info.location = self + .split(&info.address) + .and_then(|(link, target)| link.inner.location(&target)); + } + infos } /// Whether `address` is one of ours. @@ -643,16 +658,19 @@ impl Paths { stats.merge(link.inner.stats()); } let now = Instant::now(); - let table = self.table.lock_or_recover(); - let mut paths: Vec<PathInfo> = table - .paths - .keys() - .map(|address| table.info(address, now)) - .collect(); + let (mut paths, counts) = { + let table = self.table.lock_or_recover(); + let paths: Vec<PathInfo> = table + .paths + .keys() + .map(|address| table.info(address, now)) + .collect(); + (paths, table.counts) + }; paths.sort_by(|a, b| a.address.cmp(&b.address)); stats.paths = Some(PathsStats { - paths, - counts: table.counts, + paths: self.located(paths), + counts, }); stats } diff --git a/src/transport/stats.rs b/src/transport/stats.rs index 12b2c99..6b99999 100644 --- a/src/transport/stats.rs +++ b/src/transport/stats.rs @@ -47,8 +47,13 @@ pub(crate) struct PathsStats { #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct PathInfo { - /// `<scheme>:<transport identity>`. + /// `<scheme>:<transport identity>`: whom the path goes to, as the transport knows it + /// (a libp2p peer ID, a Mycelium public key, a node key for Tor). pub(crate) address: String, + /// Where the path goes now (`Transport::location`): for libp2p, the remote address of + /// its connection; for Tor, the onion service. Absent where the address says it all + /// (Mycelium), or with no connection. + pub(crate) location: Option<String>, /// Smoothed round-trip time of its PINGs (ours, and our keepalives); absent before /// an answer. pub(crate) rtt_ms: Option<f64>, diff --git a/src/transport/tor.rs b/src/transport/tor.rs index d049453..b826b4b 100644 --- a/src/transport/tor.rs +++ b/src/transport/tor.rs @@ -442,6 +442,11 @@ impl Transport for TorTransport { Some(key) } + /// The onion service, `<onion>.onion:<port>`. + fn location(&self, target: &str) -> Option<String> { + lock(&self.shared.book).get(target).cloned() + } + fn locations(&self, target: &str) -> Vec<String> { lock(&self.shared.book) .get(target) |
