1use std::sync::Arc;
3
4use derive_more::{From, Into};
5use futures::task::SpawnError;
6
7use thiserror::Error;
8use tracing::error;
9
10use retry_error::RetryError;
11use safelog::{Redacted, Sensitive};
12use tor_cell::relaycell::hs::IntroduceAckStatus;
13use tor_error::define_asref_dyn_std_error;
14use tor_error::{Bug, ErrorKind, ErrorReport as _, HasKind, HasRetryTime, RetryTime, internal};
15use tor_linkspec::RelayIds;
16use tor_llcrypto::pk::ed25519::Ed25519Identity;
17use tor_netdir::Relay;
18
19pub(crate) type RendPtIdentityForError = Redacted<RelayIds>;
21
22pub(crate) fn rend_pt_identity_for_error(relay: &Relay<'_>) -> RendPtIdentityForError {
24 RelayIds::from_relay_ids(relay).into()
25}
26
27#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, From, Into)]
33#[allow(clippy::exhaustive_structs)]
34#[derive(derive_more::Display)]
35#[display("#{}", self.0 + 1)]
36pub struct IntroPtIndex(pub usize);
37
38#[derive(Error, Clone, Debug)]
40#[non_exhaustive]
41pub enum ConnError {
42 #[error("Invalid hidden service identity (`.onion` address)")]
44 InvalidHsId,
45
46 #[error("Unable to download hidden service descriptor")]
48 DescriptorDownload(RetryError<tor_error::Report<DescriptorError>>),
49
50 #[error("Unable to connect to hidden service using any Rendezvous Point / Introduction Point")]
53 Failed(#[source] RetryError<FailedAttemptError>),
54
55 #[error("consensus contains no suitable hidden service directories")]
57 NoHsDirs,
58
59 #[error("hidden service has no introduction points usable by us")]
63 NoUsableIntroPoints,
64
65 #[error("cannot fetch descriptor (we are rate-limited)")]
71 NoUsableHsDirs,
72
73 #[error("Unable to spawn {spawning}")]
75 Spawn {
76 spawning: &'static str,
78 #[source]
80 cause: Arc<SpawnError>,
81 },
82
83 #[error("{0}")]
85 Bug(#[from] Bug),
86}
87
88#[derive(Error, Clone, Debug)]
90#[non_exhaustive]
91#[error("tried hsdir {hsdir}: {error}")]
92pub struct DescriptorError {
93 pub hsdir: Sensitive<Ed25519Identity>,
98
99 #[source]
101 pub error: DescriptorErrorDetail,
102}
103define_asref_dyn_std_error!(DescriptorError);
104
105#[derive(Error, Clone, Debug)]
107#[non_exhaustive]
108#[derive(strum::EnumDiscriminants)]
114#[strum_discriminants(derive(PartialOrd, Ord))]
115pub enum DescriptorErrorDetail {
116 #[error("timed out")]
118 Timeout,
119
120 #[error("circuit failed")]
122 Circuit(#[from] tor_circmgr::Error),
123
124 #[error("stream failed")]
126 Stream(#[source] tor_proto::Error),
127
128 #[error("directory error")]
130 Directory(#[from] tor_dirclient::RequestError),
131
132 #[error("problem with descriptor")]
134 Descriptor(#[from] tor_netdoc::doc::hsdesc::HsDescError),
135
136 #[error("{0}")]
138 Bug(#[from] Bug),
139}
140
141impl From<tor_rtcompat::TimeoutError> for DescriptorErrorDetail {
142 fn from(_: tor_rtcompat::TimeoutError) -> Self {
143 Self::Timeout
144 }
145}
146
147#[derive(Error, Clone, Debug)]
149#[non_exhaustive]
150#[derive(strum::EnumDiscriminants)]
156#[strum_discriminants(derive(PartialOrd, Ord))]
157pub enum FailedAttemptError {
159 #[error("Unusable introduction point #{intro_index}")]
161 UnusableIntro {
162 #[source]
164 error: crate::relay_info::InvalidTarget,
165
166 intro_index: IntroPtIndex,
168 },
169
170 #[error("Failed to obtain any circuit to use as a rendezvous circuit")]
172 RendezvousCircuitObtain {
173 #[source]
175 error: tor_circmgr::Error,
176 },
177
178 #[error("Creating a rendezvous circuit and rendezvous point took too long")]
180 RendezvousEstablishTimeout {
181 rend_pt: RendPtIdentityForError,
184 },
185
186 #[error("Failed to establish rendezvous point at {rend_pt}")]
188 RendezvousEstablish {
189 #[source]
191 error: tor_proto::Error,
192
193 rend_pt: RendPtIdentityForError,
196 },
197
198 #[error("Failed to obtain circuit to introduction point {intro_index}")]
200 IntroductionCircuitObtain {
201 #[source]
203 error: tor_circmgr::Error,
204
205 intro_index: IntroPtIndex,
207 },
208
209 #[error("Introduction exchange (with the introduction point) failed")]
211 IntroductionExchange {
212 #[source]
214 error: tor_proto::Error,
215
216 intro_index: IntroPtIndex,
218 },
219
220 #[error("Introduction point {intro_index} reported error in its INTRODUCE_ACK: {status}")]
222 IntroductionFailed {
223 status: IntroduceAckStatus,
225
226 intro_index: IntroPtIndex,
228 },
229
230 #[error("Communication with introduction point {intro_index} took too long")]
235 IntroductionTimeout {
236 intro_index: IntroPtIndex,
238 },
239
240 #[error("Rendezvous at {rend_pt} using introduction point {intro_index} took too long")]
245 RendezvousCompletionTimeout {
246 intro_index: IntroPtIndex,
248
249 rend_pt: RendPtIdentityForError,
252 },
253
254 #[error(
256 "Error on rendezvous circuit when expecting rendezvous completion (RENDEZVOUS2 message)"
257 )]
258 RendezvousCompletionCircuitError {
259 #[source]
261 error: tor_proto::Error,
262
263 intro_index: IntroPtIndex,
265
266 rend_pt: RendPtIdentityForError,
269 },
270
271 #[error("Rendezvous completion end-to-end crypto handshake failed (bad RENDEZVOUS2 message)")]
275 RendezvousCompletionHandshake {
276 #[source]
278 error: tor_proto::Error,
279
280 intro_index: IntroPtIndex,
282
283 rend_pt: RendPtIdentityForError,
286 },
287
288 #[error("{0}")]
290 Bug(#[from] Bug),
291}
292define_asref_dyn_std_error!(FailedAttemptError);
293
294impl FailedAttemptError {
295 pub(crate) fn intro_index(&self) -> Option<IntroPtIndex> {
301 use FailedAttemptError as FAE;
302 match self {
303 FAE::UnusableIntro { intro_index, .. }
304 | FAE::RendezvousCompletionCircuitError { intro_index, .. }
305 | FAE::RendezvousCompletionHandshake { intro_index, .. }
306 | FAE::RendezvousCompletionTimeout { intro_index, .. }
307 | FAE::IntroductionCircuitObtain { intro_index, .. }
308 | FAE::IntroductionExchange { intro_index, .. }
309 | FAE::IntroductionFailed { intro_index, .. }
310 | FAE::IntroductionTimeout { intro_index, .. } => Some(*intro_index),
311 FAE::RendezvousCircuitObtain { .. }
312 | FAE::RendezvousEstablish { .. }
313 | FAE::RendezvousEstablishTimeout { .. }
314 | FAE::Bug(_) => None,
315 }
316 }
317}
318
319impl HasRetryTime for FailedAttemptError {
337 fn retry_time(&self) -> RetryTime {
338 use FailedAttemptError as FAE;
339 use RetryTime as RT;
340 match self {
341 FAE::UnusableIntro { error, .. } => error.retry_time(),
343 FAE::RendezvousCircuitObtain { error } => error.retry_time(),
344 FAE::IntroductionCircuitObtain { error, .. } => error.retry_time(),
345 FAE::IntroductionFailed { status, .. } => status.retry_time(),
346 FAE::RendezvousCompletionCircuitError { error: _e, .. }
348 | FAE::IntroductionExchange { error: _e, .. }
349 | FAE::RendezvousEstablish { error: _e, .. } => RT::AfterWaiting,
350 FAE::RendezvousEstablishTimeout { .. }
352 | FAE::RendezvousCompletionTimeout { .. }
353 | FAE::IntroductionTimeout { .. } => RT::AfterWaiting,
354 FAE::RendezvousCompletionHandshake { error: _e, .. } => RT::Never,
357 FAE::Bug(_) => RT::Never,
358 }
359 }
360}
361
362impl HasKind for ConnError {
363 fn kind(&self) -> ErrorKind {
364 use ConnError as CE;
365 use ErrorKind as EK;
366 match self {
367 CE::InvalidHsId => EK::InvalidStreamTarget,
368 CE::NoHsDirs => EK::TorDirectoryUnusable,
369 CE::NoUsableIntroPoints => EK::OnionServiceProtocolViolation,
370 CE::Spawn { cause, .. } => cause.kind(),
371 CE::Bug(e) => e.kind(),
372
373 CE::DescriptorDownload(attempts) => attempts
374 .sources()
375 .max_by_key(|attempt| DescriptorErrorDetailDiscriminants::from(&attempt.0.error))
376 .map(|attempt| attempt.0.kind())
377 .unwrap_or_else(|| {
378 let bug = internal!("internal error, empty CE::DescriptorDownload");
379 error!("bug: {}", bug.report());
380 bug.kind()
381 }),
382
383 CE::NoUsableHsDirs => EK::OnionServiceConnectionFailed,
384
385 CE::Failed(attempts) => attempts
386 .sources()
387 .max_by_key(|attempt| FailedAttemptErrorDiscriminants::from(*attempt))
388 .map(|attempt| attempt.kind())
389 .unwrap_or_else(|| {
390 let bug = internal!("internal error, empty CE::DescriptorDownload");
391 error!("bug: {}", bug.report());
392 bug.kind()
393 }),
394 }
395 }
396}
397
398impl HasKind for DescriptorError {
399 fn kind(&self) -> ErrorKind {
400 self.error.kind()
401 }
402}
403
404impl HasKind for DescriptorErrorDetail {
405 fn kind(&self) -> ErrorKind {
406 use DescriptorErrorDetail as DED;
407 use ErrorKind as EK;
408 use tor_dirclient::RequestError as RE;
409 match self {
410 DED::Timeout => EK::TorNetworkTimeout,
411 DED::Circuit(e) => e.kind(),
412 DED::Stream(e) => e.kind(),
413 DED::Directory(RE::HttpStatus(st, _)) if *st == 404 => EK::OnionServiceNotFound,
414 DED::Directory(RE::ResponseTooLong(_)) => EK::OnionServiceProtocolViolation,
415 DED::Directory(RE::HeadersTooLong(_)) => EK::OnionServiceProtocolViolation,
416 DED::Directory(RE::Utf8Encoding(_)) => EK::OnionServiceProtocolViolation,
417 DED::Directory(other_re) => other_re.kind(),
418 DED::Descriptor(e) => e.kind(),
419 DED::Bug(e) => e.kind(),
420 }
421 }
422}
423
424impl HasKind for FailedAttemptError {
425 fn kind(&self) -> ErrorKind {
426 use ErrorKind as EK;
430 use FailedAttemptError as FAE;
431 match self {
432 FAE::UnusableIntro { .. } => EK::OnionServiceProtocolViolation,
433 FAE::RendezvousCircuitObtain { error, .. } => error.kind(),
434 FAE::RendezvousEstablish { error, .. } => error.kind(),
435 FAE::RendezvousCompletionCircuitError { error, .. } => error.kind(),
436 FAE::RendezvousCompletionHandshake { error, .. } => error.kind(),
437 FAE::RendezvousEstablishTimeout { .. } => EK::TorNetworkTimeout,
438 FAE::IntroductionCircuitObtain { error, .. } => error.kind(),
439 FAE::IntroductionExchange { error, .. } => error.kind(),
440 FAE::IntroductionFailed { .. } => EK::OnionServiceConnectionFailed,
441 FAE::IntroductionTimeout { .. } => EK::TorNetworkTimeout,
442 FAE::RendezvousCompletionTimeout { .. } => EK::RemoteNetworkTimeout,
443 FAE::Bug(e) => e.kind(),
444 }
445 }
446}
447
448#[derive(Error, Clone, Debug)]
450#[non_exhaustive]
451pub enum StartupError {
452 #[error("Unable to spawn {spawning}")]
454 Spawn {
455 spawning: &'static str,
457 #[source]
459 cause: Arc<SpawnError>,
460 },
461
462 #[error("{0}")]
464 Bug(#[from] Bug),
465}
466
467impl HasKind for StartupError {
468 fn kind(&self) -> ErrorKind {
469 use StartupError as SE;
470 match self {
471 SE::Spawn { cause, .. } => cause.kind(),
472 SE::Bug(e) => e.kind(),
473 }
474 }
475}
476
477#[derive(Error, Clone, Debug)]
483#[non_exhaustive]
484pub(crate) enum ProofOfWorkError {
485 #[error("Runtime error from client puzzle solver, #{0}")]
487 Runtime(#[from] tor_hscrypto::pow::RuntimeError),
488
489 #[error("Client puzzle parameters are not valid at this time")]
491 TimeValidity(#[from] tor_checkable::TimeValidityError),
492
493 #[error("Unexpectedly lost contact with solver task")]
495 #[allow(dead_code)]
496 SolverDisconnected,
497}
498
499impl DescriptorErrorDetail {
500 pub(crate) fn should_report_as_suspicious(&self) -> bool {
503 use DescriptorErrorDetail as E;
504 match self {
505 E::Timeout => false,
506 E::Circuit(_) => false,
507 E::Stream(_) => false, E::Directory(e) => e.should_report_as_suspicious_if_anon(),
509 E::Descriptor(e) => e.should_report_as_suspicious(),
510 E::Bug(_) => false,
511 }
512 }
513}