Skip to main content

leyline/tls/
error.rs

1use std::io;
2
3use leyline_bssl::ssl::ErrorCode;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum ProxyReply {
8    HttpStatus(u16),
9    Socks5(u8),
10}
11
12#[derive(Debug, thiserror::Error)]
13#[non_exhaustive]
14pub enum TlsError {
15    #[error("ssl config: {0}")]
16    SslConfig(String),
17
18    #[error("tls profile: {0}")]
19    Profile(String),
20
21    #[error("trust store: {0}")]
22    TrustStore(String),
23
24    #[error("tls handshake: {0}")]
25    Handshake(String),
26
27    #[error("tls handshake closed or reset by peer")]
28    Rejected(#[source] io::Error),
29
30    #[error("tls handshake io")]
31    HandshakeIo(#[source] io::Error),
32
33    #[error("certificate verification failed: {detail}")]
34    Certificate {
35        verify_code: Option<i32>,
36        reason: Option<&'static str>,
37        detail: String,
38    },
39
40    #[error("hostname: {0}")]
41    Hostname(String),
42
43    #[error("certificate pin: {0}")]
44    Pinning(String),
45
46    #[error("tcp connect")]
47    TcpConnect(#[source] io::Error),
48
49    #[error("dns resolution")]
50    Dns(#[source] io::Error),
51
52    #[error("proxy: {detail}")]
53    Proxy {
54        status: Option<u16>,
55        detail: String,
56        #[source]
57        source: Option<io::Error>,
58    },
59
60    #[error("proxy could not reach the target: {detail}")]
61    ProxyTargetUnreachable { reply: ProxyReply, detail: String },
62}
63
64fn peer_closed(kind: io::ErrorKind) -> bool {
65    matches!(
66        kind,
67        io::ErrorKind::UnexpectedEof
68            | io::ErrorKind::ConnectionReset
69            | io::ErrorKind::ConnectionAborted
70            | io::ErrorKind::BrokenPipe
71    )
72}
73
74impl TlsError {
75    pub(crate) fn from_handshake<S>(e: &leyline_bssl_tokio::HandshakeError<S>) -> Self {
76        if let Some(io) = e.as_io_error() {
77            let copy = io::Error::new(io.kind(), io.to_string());
78            if peer_closed(io.kind()) {
79                return Self::Rejected(copy);
80            }
81            return Self::HandshakeIo(copy);
82        }
83        let msg = e.to_string();
84        if e.code() == Some(ErrorCode::SYSCALL) {
85            Self::Rejected(io::Error::new(io::ErrorKind::UnexpectedEof, msg))
86        } else {
87            Self::Handshake(msg)
88        }
89    }
90
91    pub(crate) fn from_stack(e: leyline_bssl::error::ErrorStack) -> Self {
92        Self::SslConfig(e.to_string())
93    }
94
95    pub(crate) fn proxy(detail: impl Into<String>) -> Self {
96        Self::Proxy {
97            status: None,
98            detail: detail.into(),
99            source: None,
100        }
101    }
102
103    pub(crate) fn proxy_io(source: io::Error) -> Self {
104        Self::Proxy {
105            status: None,
106            detail: "proxy connection failed".into(),
107            source: Some(source),
108        }
109    }
110
111    pub(crate) fn proxy_target(reply: ProxyReply, detail: impl Into<String>) -> Self {
112        Self::ProxyTargetUnreachable {
113            reply,
114            detail: detail.into(),
115        }
116    }
117
118    pub(crate) fn into_proxy(self) -> Self {
119        match self {
120            Self::Proxy { .. } | Self::ProxyTargetUnreachable { .. } => self,
121            Self::Dns(e) | Self::TcpConnect(e) | Self::HandshakeIo(e) | Self::Rejected(e) => {
122                Self::proxy_io(e)
123            }
124            other => Self::proxy(other.to_string()),
125        }
126    }
127
128    pub(crate) fn is_handshake(&self) -> bool {
129        matches!(
130            self,
131            Self::Handshake(_) | Self::HandshakeIo(_) | Self::Rejected(_)
132        )
133    }
134
135    pub(crate) fn is_connect_phase(&self) -> bool {
136        matches!(self, Self::Dns(_) | Self::TcpConnect(_)) || self.is_handshake()
137    }
138
139    pub(crate) fn io_source(&self) -> Option<&io::Error> {
140        match self {
141            Self::TcpConnect(e) | Self::Dns(e) | Self::HandshakeIo(e) | Self::Rejected(e) => {
142                Some(e)
143            }
144            Self::Proxy { source, .. } => source.as_ref(),
145            _ => None,
146        }
147    }
148
149    pub(crate) fn duplicate(&self) -> Self {
150        let io = |e: &io::Error| io::Error::new(e.kind(), e.to_string());
151        match self {
152            Self::SslConfig(m) => Self::SslConfig(m.clone()),
153            Self::Profile(m) => Self::Profile(m.clone()),
154            Self::TrustStore(m) => Self::TrustStore(m.clone()),
155            Self::Handshake(m) => Self::Handshake(m.clone()),
156            Self::Rejected(e) => Self::Rejected(io(e)),
157            Self::HandshakeIo(e) => Self::HandshakeIo(io(e)),
158            Self::Certificate {
159                verify_code,
160                reason,
161                detail,
162            } => Self::Certificate {
163                verify_code: *verify_code,
164                reason: *reason,
165                detail: detail.clone(),
166            },
167            Self::Hostname(m) => Self::Hostname(m.clone()),
168            Self::Pinning(m) => Self::Pinning(m.clone()),
169            Self::TcpConnect(e) => Self::TcpConnect(io(e)),
170            Self::Dns(e) => Self::Dns(io(e)),
171            Self::Proxy {
172                status,
173                detail,
174                source,
175            } => Self::Proxy {
176                status: *status,
177                detail: detail.clone(),
178                source: source.as_ref().map(io),
179            },
180            Self::ProxyTargetUnreachable { reply, detail } => Self::ProxyTargetUnreachable {
181                reply: *reply,
182                detail: detail.clone(),
183            },
184        }
185    }
186}