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}