From bd21360f874f6e3d002c0840d7f8722e4fa649e9 Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 29 Oct 2026 05:34:25 +0100 Subject: libp2p: add punching stats --- docs/openapi.yaml | 9 +++++++++ src/transport/identity.rs | 7 ++++++- src/transport/libp2p.rs | 11 +++++++++++ src/transport/mod.rs | 20 ++++++++++++++++++++ src/transport/paths.rs | 6 ++++++ 5 files changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index bc9652e..6de726c 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1149,6 +1149,15 @@ components: type: [string, 'null'] enum: [direct, relayed, null] description: The libp2p connection to it; null if none, or on a transport without connections (Mycelium). + punching: + type: object + description: 'While it is connected only through a relay and a hole punch failed: how often in a row, and when the next try is (after 1, 5, then every 30 minutes). Absent otherwise.' + required: [failed, nextInSecs] + properties: + failed: + type: integer + nextInSecs: + type: integer paths: type: array description: Every address it proved itself at, best first, as the paths layer measures them. diff --git a/src/transport/identity.rs b/src/transport/identity.rs index c597d77..303177a 100644 --- a/src/transport/identity.rs +++ b/src/transport/identity.rs @@ -44,7 +44,7 @@ use crate::transport::paths::{Gate, PathHandler, Paths, scheme_of}; use crate::transport::stats::{IdentityCounts, IdentityStats, TransportStats}; use crate::transport::traffic::Traffic; use crate::transport::{ - Destination, EncodedMessage, HandlerFn, IdentityProof, MessageHandler, MessageKind, + Connection, Destination, EncodedMessage, HandlerFn, IdentityProof, MessageHandler, MessageKind, ParsedMessage, Transport, }; @@ -554,10 +554,15 @@ impl Transport for IdentityTransport { None => (self.shared.hinted_at(peer).into_iter().next()?, false), }; let connection = self.shared.paths.connection(&address); + let punching = match connection { + Some(Connection::Relayed) => self.shared.paths.punching(&address), + _ => None, + }; Some(Destination { address: self.shared.paths.location(&address), proven: is_proven, connection, + punching, paths: self.shared.paths.info(&proven), }) } diff --git a/src/transport/libp2p.rs b/src/transport/libp2p.rs index a5541f6..610b94e 100644 --- a/src/transport/libp2p.rs +++ b/src/transport/libp2p.rs @@ -2334,6 +2334,17 @@ impl Transport for Libp2pTransport { lock(&self.book).connection(&peer) } + fn punching(&self, target: &str) -> Option { + let peer = PeerId::from_str(target).ok()?; + let (failed, next) = *lock(&self.book).punching.get(&peer)?; + Some(crate::transport::Punching { + failed, + next_in_secs: next + .saturating_duration_since(tokio::time::Instant::now()) + .as_secs(), + }) + } + fn location(&self, target: &str) -> Option { let peer = PeerId::from_str(target).ok()?; let address = lock(&self.book).location(&peer)?.clone(); diff --git a/src/transport/mod.rs b/src/transport/mod.rs index fb13ad9..40fdfc0 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -361,11 +361,24 @@ pub(crate) struct Destination { pub(crate) proven: bool, /// The transport's connection to it, if the transport has connections. pub(crate) connection: Option, + /// While it is connected only through a relay: how hole punching went. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) punching: Option, /// Every address it proved itself at, best first, as the paths layer measures them /// (`transport::paths`). pub(crate) paths: Vec, } +/// Hole punching to a peer connected only through a relay (libp2p), once a punch failed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct Punching { + /// Punches that failed in a row. + pub(crate) failed: usize, + /// When it is tried again, in seconds from now (0: due). + pub(crate) next_in_secs: u64, +} + /// How a transport with connections (libp2p) is connected to a peer. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "camelCase")] @@ -712,6 +725,13 @@ pub(crate) trait Transport: Send + Sync { None } + /// How hole punching to `target` went, if a punch failed and it is still connected + /// only through a relay (libp2p). By default never. + fn punching(&self, target: &str) -> Option { + let _ = target; + None + } + /// The smoothed round-trip time of the path messages to `peer` go over, if /// measured (`transport::paths`). By default none. fn rtt(&self, peer: &str) -> Option { diff --git a/src/transport/paths.rs b/src/transport/paths.rs index 13fc6db..e8858da 100644 --- a/src/transport/paths.rs +++ b/src/transport/paths.rs @@ -706,6 +706,12 @@ impl Paths { link.inner.connection(&target) } + /// How hole punching to `address` went (`Transport::punching`). + pub(crate) fn punching(&self, address: &str) -> Option { + let (link, target) = self.split(address)?; + link.inner.punching(&target) + } + /// Paths' own figures, and every transport's. pub(crate) fn stats(&self) -> TransportStats { let mut stats = TransportStats::default(); -- cgit v1.3.1