1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
//! What libp2p's errors say, for the log and the figures: dial failures, relay
//! reservations refused or lost.
use super::*;
/// 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.
pub(super) 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 address = without_peer(address.clone());
format!("{address}: {}", innermost_causes(error))
})
.collect();
if described.is_empty() {
return error.to_string();
}
described.join("; ")
}
/// Why a slot on a relay ended, or was never granted, shortly: libp2p nests the
/// relay's answer several types deep (`Left(Left(Left(Reservation(...))))`), and the
/// common cases deserve a sentence.
pub(super) fn relay_failure(reason: &std::io::Result<()>) -> String {
let Err(error) = reason else {
return "closed".to_owned();
};
relay_failure_text(&format!("{error:?}"), &innermost_causes(error))
}
/// `relay_failure` on the error's debug form, and its innermost cause as text.
pub(super) fn relay_failure_text(debug: &str, innermost: &str) -> String {
let known = [
(
"NoAddressesInReservation",
"the relay gave no address to be reached through it",
),
("ResourceLimitExceeded", "the relay has no slot left"),
("PermissionDenied", "the relay doesn't let us reserve"),
("NoReservation", "the relay holds no slot for us"),
(
"connection gated",
"the relay refused the connection (connection gated)",
),
(
"UnsupportedProtocol",
"the relay doesn't relay (no circuit relay protocol)",
),
("Timeout", "the relay didn't answer in time"),
];
if let Some((_, said)) = known.iter().find(|(pattern, _)| debug.contains(pattern)) {
return (*said).to_owned();
}
// The protocol refused, but only after its first data (`swarm_config`).
if innermost == "Failed" {
return "the relay refused the circuit relay protocol (is it a relay?)".to_owned();
}
innermost.to_owned()
}
/// Whether a slot ended, or wasn't granted, because the relay refused it (its error's
/// debug form and innermost cause, as for `relay_failure_text`), rather than because it
/// didn't answer or the connection closed: only then is it worth not asking again for a
/// while.
pub(super) fn relay_refused(debug: &str, innermost: &str) -> bool {
let refusals = [
"NoAddressesInReservation",
"ResourceLimitExceeded",
"PermissionDenied",
"connection gated",
"UnsupportedProtocol",
"WrongPeerId",
// `Ignoring`: in a range left out, whoever dialled.
"IgnoredAddress",
];
refusals.iter().any(|pattern| debug.contains(pattern)) || innermost == "Failed"
}
/// 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.
pub(super) 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(", ")
}
/// Counts a dial that failed with `error` (not one that wasn't needed).
pub(super) fn count_dial_failure(failures: &mut DialFailures, error: &::libp2p::swarm::DialError) {
use ::libp2p::swarm::DialError;
match error {
DialError::DialPeerConditionFalse(_) => {}
DialError::Transport(_) => failures.unreachable += 1,
DialError::WrongPeerId { .. } => failures.wrong_peer_id += 1,
DialError::NoAddresses => failures.no_addresses += 1,
_ => failures.other += 1,
}
}
|