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 on {0}: {1}")]
57 Discovery(SocketAddr, 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(address) = kind {
75 return Self::Discovery(address, 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) => {
117 #[allow(clippy::match_same_arms)]
118 match err {
119 EthHandshakeError::NoResponse => {
120 false
122 }
123 EthHandshakeError::InvalidFork(_) => {
124 false
128 }
129 _ => true,
130 }
131 }
132 _ => false,
133 }
134 }
135
136 fn is_fatal_protocol_error(&self) -> bool {
137 match self {
138 Self::P2PStreamError(err) => {
139 matches!(
140 err,
141 P2PStreamError::HandshakeError(
142 P2PHandshakeError::NoSharedCapabilities |
143 P2PHandshakeError::HelloNotInHandshake |
144 P2PHandshakeError::NonHelloMessageInHandshake |
145 P2PHandshakeError::Disconnected(
146 DisconnectReason::UselessPeer |
147 DisconnectReason::IncompatibleP2PProtocolVersion |
148 DisconnectReason::ProtocolBreach
149 )
150 ) | P2PStreamError::UnknownReservedMessageId(_) |
151 P2PStreamError::EmptyProtocolMessage |
152 P2PStreamError::ParseSharedCapability(_) |
153 P2PStreamError::CapabilityNotShared |
154 P2PStreamError::Disconnected(
155 DisconnectReason::UselessPeer |
156 DisconnectReason::IncompatibleP2PProtocolVersion |
157 DisconnectReason::ProtocolBreach
158 ) |
159 P2PStreamError::MismatchedProtocolVersion { .. }
160 )
161 }
162 Self::EthHandshakeError(err) => {
163 #[allow(clippy::match_same_arms)]
164 match err {
165 EthHandshakeError::NoResponse => {
166 false
168 }
169 EthHandshakeError::InvalidFork(_) => {
170 false
174 }
175 _ => true,
176 }
177 }
178 _ => false,
179 }
180 }
181
182 fn should_backoff(&self) -> Option<BackoffKind> {
183 if let Some(err) = self.as_io() {
184 return err.should_backoff()
185 }
186
187 if let Some(err) = self.as_disconnected() {
188 return match err {
189 DisconnectReason::TooManyPeers |
190 DisconnectReason::AlreadyConnected |
191 DisconnectReason::PingTimeout |
192 DisconnectReason::DisconnectRequested |
193 DisconnectReason::TcpSubsystemError => Some(BackoffKind::Low),
194
195 DisconnectReason::ProtocolBreach |
196 DisconnectReason::UselessPeer |
197 DisconnectReason::IncompatibleP2PProtocolVersion |
198 DisconnectReason::NullNodeIdentity |
199 DisconnectReason::ClientQuitting |
200 DisconnectReason::UnexpectedHandshakeIdentity |
201 DisconnectReason::ConnectedToSelf |
202 DisconnectReason::SubprotocolSpecific => {
203 Some(BackoffKind::High)
206 }
207 }
208 }
209
210 match self {
213 Self::EthHandshakeError(EthHandshakeError::NoResponse) |
215 Self::P2PStreamError(
216 P2PStreamError::HandshakeError(P2PHandshakeError::NoResponse) |
217 P2PStreamError::PingTimeout,
218 ) => Some(BackoffKind::Low),
219 Self::P2PStreamError(
221 P2PStreamError::Rlp(_) |
222 P2PStreamError::UnknownReservedMessageId(_) |
223 P2PStreamError::UnknownDisconnectReason(_) |
224 P2PStreamError::MessageTooBig { .. } |
225 P2PStreamError::EmptyProtocolMessage |
226 P2PStreamError::PingerError(_) |
227 P2PStreamError::Snap(_),
228 ) => Some(BackoffKind::Medium),
229 Self::EthHandshakeError(EthHandshakeError::InvalidFork(_)) => {
230 Some(BackoffKind::Medium)
233 }
234 _ => None,
235 }
236 }
237}
238
239impl SessionError for PendingSessionHandshakeError {
240 fn merits_discovery_ban(&self) -> bool {
241 match self {
242 Self::Eth(eth) => eth.merits_discovery_ban(),
243 Self::Ecies(err) => matches!(
244 err.inner(),
245 ECIESErrorImpl::TagCheckDecryptFailed |
246 ECIESErrorImpl::TagCheckHeaderFailed |
247 ECIESErrorImpl::TagCheckBodyFailed |
248 ECIESErrorImpl::InvalidAuthData |
249 ECIESErrorImpl::InvalidAckData |
250 ECIESErrorImpl::InvalidHeader |
251 ECIESErrorImpl::Secp256k1(_) |
252 ECIESErrorImpl::InvalidHandshake { .. }
253 ),
254 Self::Timeout | Self::UnsupportedExtraCapability => false,
255 }
256 }
257
258 fn is_fatal_protocol_error(&self) -> bool {
259 match self {
260 Self::Eth(eth) => eth.is_fatal_protocol_error(),
261 Self::Ecies(err) => matches!(
262 err.inner(),
263 ECIESErrorImpl::TagCheckDecryptFailed |
264 ECIESErrorImpl::TagCheckHeaderFailed |
265 ECIESErrorImpl::TagCheckBodyFailed |
266 ECIESErrorImpl::InvalidAuthData |
267 ECIESErrorImpl::InvalidAckData |
268 ECIESErrorImpl::InvalidHeader |
269 ECIESErrorImpl::Secp256k1(_) |
270 ECIESErrorImpl::InvalidHandshake { .. }
271 ),
272 Self::Timeout => false,
273 Self::UnsupportedExtraCapability => true,
274 }
275 }
276
277 fn should_backoff(&self) -> Option<BackoffKind> {
278 match self {
279 Self::Eth(eth) => eth.should_backoff(),
280 Self::Ecies(_) => Some(BackoffKind::Low),
281 Self::Timeout => Some(BackoffKind::Medium),
282 Self::UnsupportedExtraCapability => Some(BackoffKind::High),
283 }
284 }
285}
286
287impl SessionError for io::Error {
288 fn merits_discovery_ban(&self) -> bool {
289 false
290 }
291
292 fn is_fatal_protocol_error(&self) -> bool {
293 false
294 }
295
296 fn should_backoff(&self) -> Option<BackoffKind> {
297 match self.kind() {
298 ErrorKind::ConnectionReset | ErrorKind::BrokenPipe => Some(BackoffKind::Low),
302 ErrorKind::ConnectionRefused => {
303 Some(BackoffKind::High)
305 }
306 _ => Some(BackoffKind::Medium),
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use std::net::{Ipv4Addr, SocketAddrV4};
315
316 #[test]
317 fn test_is_fatal_disconnect() {
318 let err = PendingSessionHandshakeError::Eth(EthStreamError::P2PStreamError(
319 P2PStreamError::HandshakeError(P2PHandshakeError::Disconnected(
320 DisconnectReason::UselessPeer,
321 )),
322 ));
323
324 assert!(err.is_fatal_protocol_error());
325 }
326
327 #[test]
328 fn test_should_backoff() {
329 let err = EthStreamError::P2PStreamError(P2PStreamError::HandshakeError(
330 P2PHandshakeError::Disconnected(DisconnectReason::TooManyPeers),
331 ));
332
333 assert_eq!(err.as_disconnected(), Some(DisconnectReason::TooManyPeers));
334 assert_eq!(err.should_backoff(), Some(BackoffKind::Low));
335
336 let err = EthStreamError::P2PStreamError(P2PStreamError::HandshakeError(
337 P2PHandshakeError::NoResponse,
338 ));
339 assert_eq!(err.should_backoff(), Some(BackoffKind::Low));
340 }
341
342 #[test]
343 fn test_address_in_use_message() {
344 let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 1234));
345 let kinds = [ServiceKind::Discovery(addr), ServiceKind::Listener(addr)];
346
347 for kind in &kinds {
348 let err = NetworkError::AddressAlreadyInUse {
349 kind: *kind,
350 error: io::Error::from(ErrorKind::AddrInUse),
351 };
352
353 assert!(err.to_string().contains(kind.flags()));
354 }
355 }
356}