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) => !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}