summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-28 12:28:43 +0100
committertv <tv@krebsco.de>2026-10-28 12:28:43 +0100
commitae92c0da76e7c5c84b4cbad0812d75b2e9c43073 (patch)
tree391ad05a26d78ac89a4eedb8135286eb50fecd39 /src
parent6f1520202f1ecf7910421bf6342f2920daebca8d (diff)
tidy up libp2p dial errors
Diffstat (limited to 'src')
-rw-r--r--src/transport/libp2p.rs85
1 files changed, 83 insertions, 2 deletions
diff --git a/src/transport/libp2p.rs b/src/transport/libp2p.rs
index ba97fb5..ebf50b4 100644
--- a/src/transport/libp2p.rs
+++ b/src/transport/libp2p.rs
@@ -243,6 +243,62 @@ struct Counts {
}
/// Counts a dial that failed with `error` (not one that wasn't needed).
+/// A dial error, shortly: for each address tried (without the peer ID the caller names
+/// already), the innermost cause, once. libp2p's own text nests each transport's errors
+/// in the next ("Multiple dial errors occurred: ..."), repeating the cause at each level.
+fn describe_dial_error(error: &::libp2p::swarm::DialError) -> String {
+ use ::libp2p::swarm::DialError;
+ let DialError::Transport(errors) = error else {
+ return error.to_string();
+ };
+ let described: Vec<String> = errors
+ .iter()
+ .map(|(address, error)| {
+ let mut address = address.clone();
+ if let Some(Protocol::P2p(_)) = address.iter().last() {
+ address.pop();
+ }
+ format!("{address}: {}", innermost_causes(error))
+ })
+ .collect();
+ if described.is_empty() {
+ return error.to_string();
+ }
+ described.join("; ")
+}
+
+/// The innermost of `error`'s causes, each distinct one once: those libp2p lists in a
+/// "Multiple dial errors occurred:" text (one per line, ` - <cause>: <its cause>...`),
+/// or the last of the source chain.
+fn innermost_causes(error: &(dyn std::error::Error + 'static)) -> String {
+ let mut innermost = error;
+ while let Some(source) = innermost.source() {
+ innermost = source;
+ }
+ let text = innermost.to_string();
+ let mut causes: Vec<&str> = Vec::new();
+ for line in text.lines() {
+ let Some(listed) = line.trim().strip_prefix("- ") else {
+ continue;
+ };
+ // A line ends in the next level's heading when that level lists more.
+ let cause = listed
+ .split(": ")
+ .filter(|part| !part.starts_with("Multiple dial errors"))
+ .last()
+ .unwrap_or(listed)
+ .trim();
+ if !cause.is_empty() && !causes.contains(&cause) {
+ causes.push(cause);
+ }
+ }
+ if causes.is_empty() {
+ let cause = text.rsplit(": ").next().unwrap_or(&text).trim();
+ return cause.to_owned();
+ }
+ causes.join(", ")
+}
+
fn count_dial_failure(failures: &mut DialFailures, error: &::libp2p::swarm::DialError) {
use ::libp2p::swarm::DialError;
match error {
@@ -1081,14 +1137,14 @@ async fn run(
SwarmEvent::OutgoingConnectionError { connection_id, peer_id, error } => {
count_dial_failure(&mut lock(&book).counts.dial_failures, &error);
if let Some((_, done)) = identifying.remove(&connection_id) {
- let _ = done.send(Err(error.to_string()));
+ let _ = done.send(Err(describe_dial_error(&error)));
}
if let Some(peer) = peer_id
&& !swarm.is_connected(&peer)
{
if let Some(waiting) = connecting.remove(&peer) {
for done in waiting {
- let _ = done.send(Err(error.to_string()));
+ let _ = done.send(Err(describe_dial_error(&error)));
}
find_on_dht(&mut swarm, peer);
}
@@ -1903,6 +1959,31 @@ mod tests {
assert_eq!(book.prune(later, |_| false), 1); // `connected`, once it isn't.
}
+ /// libp2p's nested dial errors come out as each address's cause, once.
+ #[test]
+ fn dial_errors_are_short() {
+ use ::libp2p::core::transport::TransportError;
+ let peer = peer();
+ let address: Multiaddr = format!("/ip6/fd00::1/tcp/9661/p2p/{peer}").parse().unwrap();
+ let nested = std::io::Error::other(
+ "Multiple dial errors occurred:\n - Network is unreachable (os error 101): Multiple dial errors occurred:\n - Network is unreachable (os error 101): Network is unreachable (os error 101)",
+ );
+ let error =
+ ::libp2p::swarm::DialError::Transport(vec![(address, TransportError::Other(nested))]);
+ assert_eq!(
+ describe_dial_error(&error),
+ "/ip6/fd00::1/tcp/9661: Network is unreachable (os error 101)"
+ );
+ let refused = ::libp2p::swarm::DialError::Transport(vec![(
+ "/ip4/10.0.0.1/tcp/1".parse().unwrap(),
+ TransportError::Other(std::io::Error::from(std::io::ErrorKind::ConnectionRefused)),
+ )]);
+ assert_eq!(
+ describe_dial_error(&refused),
+ "/ip4/10.0.0.1/tcp/1: connection refused"
+ );
+ }
+
/// A relay doesn't dial the nodes it relays for through itself.
#[test]
fn no_circuits_through_ourselves() {