reth_network/
error.rs
1use crate::session::PendingSessionHandshakeError;
4use reth_dns_discovery::resolver::ResolveError;
5use reth_ecies::ECIESErrorImpl;
6use reth_eth_wire::{
7 errors::{EthHandshakeError, EthStreamError, P2PHandshakeError, P2PStreamError},
8 DisconnectReason,
9};
10use reth_network_types::BackoffKind;
11use std::{fmt, io, io::ErrorKind, net::SocketAddr};
12
13#[derive(Debug, PartialEq, Eq, Copy, Clone)]
15pub enum ServiceKind {
16 Listener(SocketAddr),
18 Discovery(SocketAddr),
20}
21
22impl ServiceKind {
23 pub const fn flags(&self) -> &'static str {
25 match self {
26 Self::Listener(_) => "--port",
27 Self::Discovery(_) => "--discovery.port",
28 }
29 }
30}
31
32impl fmt::Display for ServiceKind {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 Self::Listener(addr) => write!(f, "{addr} (listener service)"),
36 Self::Discovery(addr) => write!(f, "{addr} (discovery service)"),
37 }
38 }
39}
40
41#[derive(Debug, thiserror::Error)]
43pub enum NetworkError {
44 #[error(transparent)]
46 Io(#[from] io::Error),
47 #[error("address {kind} is already in use (os error 98). Choose a different port using {}", kind.flags())]
49 AddressAlreadyInUse {
50 kind: ServiceKind,
52 error: io::Error,
54 },
55 #[error("failed to launch discovery service: {0}")]
57 Discovery(io::Error),
58 #[error("discv5 error, {0}")]
60 Discv5Error(#[from] reth_discv5::Error),
61 #[error("failed to configure DNS resolver: {0}")]
65 DnsResolver(#[from] ResolveError),
66}
67
68impl NetworkError {
69 pub fn from_io_error(err: io::Error, kind: ServiceKind) -> Self {
71 match err.kind() {
72 ErrorKind::AddrInUse => Self::AddressAlreadyInUse { kind, error: err },
73 _ => {
74 if let ServiceKind::Discovery(_) = kind {
75 return Self::Discovery(err)
76 }
77 Self::Io(err)
78 }
79 }
80 }
81}
82
83#[auto_impl::auto_impl(&)]
85pub(crate) trait SessionError: fmt::Debug + fmt::Display {
86 fn merits_discovery_ban(&self) -> bool;
89
90 fn is_fatal_protocol_error(&self) -> bool;
98
99 fn should_backoff(&self) -> Option<BackoffKind>;
107}
108
109impl SessionError for EthStreamError {
110 fn merits_discovery_ban(&self) -> bool {
111 match self {
112 Self::P2PStreamError(P2PStreamError::HandshakeError(
113 P2PHandshakeError::HelloNotInHandshake |
114 P2PHandshakeError::NonHelloMessageInHandshake,
115 )) => true,
116 Self::EthHandshakeError(err) => !matches!(err, EthHandshakeError::NoResponse),
117 _ => false,
118 }
119 }
120
121 fn is_fatal_protocol_error(&self) -> bool {
122 match self {
123 Self::P2PStreamError(err) => {
124 matches!(
125 err,
126 P2PStreamError::HandshakeError(
127 P2PHandshakeError::NoSharedCapabilities |
128 P2PHandshakeError::HelloNotInHandshake |
129 P2PHandshakeError::NonHelloMessageInHandshake |
130 P2PHandshakeError::Disconnected(
131 DisconnectReason::UselessPeer |
132 DisconnectReason::IncompatibleP2PProtocolVersion |
133 DisconnectReason::ProtocolBreach
134 )
135 ) | P2PStreamError::UnknownReservedMessageId(_) |
136 P2PStreamError::EmptyProtocolMessage |
137 P2PStreamError::ParseSharedCapability(_) |
138 P2PStreamError::CapabilityNotShared |
139 P2PStreamError::Disconnected(
140 DisconnectReason::UselessPeer |
141 DisconnectReason::IncompatibleP2PProtocolVersion |
142 DisconnectReason::ProtocolBreach
143 ) |
144 P2PStreamError::MismatchedProtocolVersion { .. }
145 )
146 }
147 Self::EthHandshakeError(err) => !matches!(err, EthHandshakeError::NoResponse),
148 _ => false,
149 }
150 }
151
152 fn should_backoff(&self) -> Option<BackoffKind> {
153 if let Some(err) = self.as_io() {
154 return err.should_backoff()
155 }
156
157 if let Some(err) = self.as_disconnected() {
158 return match err {
159 DisconnectReason::TooManyPeers |
160 DisconnectReason::AlreadyConnected |
161 DisconnectReason::PingTimeout |
162 DisconnectReason::DisconnectRequested |
163 DisconnectReason::TcpSubsystemError => Some(BackoffKind::Low),
164
165 DisconnectReason::ProtocolBreach |
166 DisconnectReason::UselessPeer |
167 DisconnectReason::IncompatibleP2PProtocolVersion |
168 DisconnectReason::NullNodeIdentity |
169 DisconnectReason::ClientQuitting |
170 DisconnectReason::UnexpectedHandshakeIdentity |
171 DisconnectReason::ConnectedToSelf |
172 DisconnectReason::SubprotocolSpecific => {
173 Some(BackoffKind::High)
176 }
177 }
178 }
179
180 match self {
183 Self::EthHandshakeError(EthHandshakeError::NoResponse) |
185 Self::P2PStreamError(
186 P2PStreamError::HandshakeError(P2PHandshakeError::NoResponse) |
187 P2PStreamError::PingTimeout,
188 ) => Some(BackoffKind::Low),
189 Self::P2PStreamError(
191 P2PStreamError::Rlp(_) |
192 P2PStreamError::UnknownReservedMessageId(_) |
193 P2PStreamError::UnknownDisconnectReason(_) |
194 P2PStreamError::MessageTooBig { .. } |
195 P2PStreamError::EmptyProtocolMessage |
196 P2PStreamError::PingerError(_) |
197 P2PStreamError::Snap(_),
198 ) => Some(BackoffKind::Medium),
199 _ => None,
200 }
201 }
202}
203
204impl SessionError for PendingSessionHandshakeError {
205 fn merits_discovery_ban(&self) -> bool {
206 match self {
207 Self::Eth(eth) => eth.merits_discovery_ban(),
208 Self::Ecies(err) => matches!(
209 err.inner(),
210 ECIESErrorImpl::TagCheckDecryptFailed |
211 ECIESErrorImpl::TagCheckHeaderFailed |
212 ECIESErrorImpl::TagCheckBodyFailed |
213 ECIESErrorImpl::InvalidAuthData |
214 ECIESErrorImpl::InvalidAckData |
215 ECIESErrorImpl::InvalidHeader |
216 ECIESErrorImpl::Secp256k1(_) |
217 ECIESErrorImpl::InvalidHandshake { .. }
218 ),
219 Self::Timeout | Self::UnsupportedExtraCapability => false,
220 }
221 }
222
223 fn is_fatal_protocol_error(&self) -> bool {
224 match self {
225 Self::Eth(eth) => eth.is_fatal_protocol_error(),
226 Self::Ecies(err) => matches!(
227 err.inner(),
228 ECIESErrorImpl::TagCheckDecryptFailed |
229 ECIESErrorImpl::TagCheckHeaderFailed |
230 ECIESErrorImpl::TagCheckBodyFailed |
231 ECIESErrorImpl::InvalidAuthData |
232 ECIESErrorImpl::InvalidAckData |
233 ECIESErrorImpl::InvalidHeader |
234 ECIESErrorImpl::Secp256k1(_) |
235 ECIESErrorImpl::InvalidHandshake { .. }
236 ),
237 Self::Timeout => false,
238 Self::UnsupportedExtraCapability => true,
239 }
240 }
241
242 fn should_backoff(&self) -> Option<BackoffKind> {
243 match self {
244 Self::Eth(eth) => eth.should_backoff(),
245 Self::Ecies(_) => Some(BackoffKind::Low),
246 Self::Timeout => Some(BackoffKind::Medium),
247 Self::UnsupportedExtraCapability => Some(BackoffKind::High),
248 }
249 }
250}
251
252impl SessionError for io::Error {
253 fn merits_discovery_ban(&self) -> bool {
254 false
255 }
256
257 fn is_fatal_protocol_error(&self) -> bool {
258 false
259 }
260
261 fn should_backoff(&self) -> Option<BackoffKind> {
262 match self.kind() {
263 ErrorKind::ConnectionReset | ErrorKind::BrokenPipe => Some(BackoffKind::Low),
267 ErrorKind::ConnectionRefused => {
268 Some(BackoffKind::High)
270 }
271 _ => Some(BackoffKind::Medium),
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use std::net::{Ipv4Addr, SocketAddrV4};
280
281 #[test]
282 fn test_is_fatal_disconnect() {
283 let err = PendingSessionHandshakeError::Eth(EthStreamError::P2PStreamError(
284 P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected(
285 DisconnectReason::UselessPeer,
286 )),
287 ));
288
289 assert!(err.is_fatal_protocol_error());
290 }
291
292 #[test]
293 fn test_should_backoff() {
294 let err = EthStreamError::P2PStreamError(P2PStreamError::HandshakeError(
295 P2PHandshakeError::Disconnected(DisconnectReason::TooManyPeers),
296 ));
297
298 assert_eq!(err.as_disconnected(), Some(DisconnectReason::TooManyPeers));
299 assert_eq!(err.should_backoff(), Some(BackoffKind::Low));
300
301 let err = EthStreamError::P2PStreamError(P2PStreamError::HandshakeError(
302 P2PHandshakeError::NoResponse,
303 ));
304 assert_eq!(err.should_backoff(), Some(BackoffKind::Low));
305 }
306
307 #[test]
308 fn test_address_in_use_message() {
309 let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1234));
310 let kinds = [ServiceKind::Discovery(addr), ServiceKind::Listener(addr)];
311
312 for kind in &kinds {
313 let err = NetworkError::AddressAlreadyInUse {
314 kind: *kind,
315 error: io::Error::from(ErrorKind::AddrInUse),
316 };
317
318 assert!(err.to_string().contains(kind.flags()));
319 }
320 }
321}