diff options
Diffstat (limited to 'src/transport')
| -rw-r--r-- | src/transport/libp2p.rs | 85 |
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() { |
