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("descriptor was not compatible with network parameters: {0}")]
134 ParameterMismatch(String),
135
136 #[error("descriptor is outside its validity period")]
138 OutsideValidityPeriod(#[from] tor_checkable::TimeValidityError),
139
140 #[error("problem with descriptor")]
142 Descriptor(#[from] tor_netdoc::doc::hsdesc::HsDescError),
143
144 #[error("{0}")]
146 Bug(#[from] Bug),
147}
148
149impl From<tor_rtcompat::TimeoutError> for DescriptorErrorDetail {
150 fn from(_: tor_rtcompat::TimeoutError) -> Self {
151 Self::Timeout
152 }
153}
154
155#[derive(Error, Clone, Debug)]
157#[non_exhaustive]
158#[derive(strum::EnumDiscriminants)]
164#[strum_discriminants(derive(PartialOrd, Ord))]
165pub enum FailedAttemptError {
167 #[error("Unusable introduction point #{intro_index}")]
169 UnusableIntro {
170 #[source]
172 error: crate::relay_info::InvalidTarget,
173
174 intro_index: IntroPtIndex,
176 },
177
178 #[error("Failed to obtain any circuit to use as a rendezvous circuit")]
180 RendezvousCircuitObtain {
181 #[source]
183 error: tor_circmgr::Error,
184 },
185
186 #[error("Creating a rendezvous circuit and rendezvous point took too long")]
188 RendezvousEstablishTimeout {
189 rend_pt: RendPtIdentityForError,
192 },
193
194 #[error("Failed to establish rendezvous point at {rend_pt}")]
196 RendezvousEstablish {
197 #[source]
199 error: tor_proto::Error,
200
201 rend_pt: RendPtIdentityForError,
204 },
205
206 #[error("Failed to obtain circuit to introduction point {intro_index}")]
208 IntroductionCircuitObtain {
209 #[source]
211 error: tor_circmgr::Error,
212
213 intro_index: IntroPtIndex,
215 },
216
217 #[error("Introduction exchange (with the introduction point) failed")]
219 IntroductionExchange {
220 #[source]
222 error: tor_proto::Error,
223
224 intro_index: IntroPtIndex,
226 },
227
228 #[error("Introduction point {intro_index} reported error in its INTRODUCE_ACK: {status}")]
230 IntroductionFailed {
231 status: IntroduceAckStatus,
233
234 intro_index: IntroPtIndex,
236 },
237
238 #[error("Communication with introduction point {intro_index} took too long")]
243 IntroductionTimeout {
244 intro_index: IntroPtIndex,
246 },
247
248 #[error("Rendezvous at {rend_pt} using introduction point {intro_index} took too long")]
253 RendezvousCompletionTimeout {
254 intro_index: IntroPtIndex,
256
257 rend_pt: RendPtIdentityForError,
260 },
261
262 #[error(
264 "Error on rendezvous circuit when expecting rendezvous completion (RENDEZVOUS2 message)"
265 )]
266 RendezvousCompletionCircuitError {
267 #[source]
269 error: tor_proto::Error,
270
271 intro_index: IntroPtIndex,
273
274 rend_pt: RendPtIdentityForError,
277 },
278
279 #[error("Rendezvous completion end-to-end crypto handshake failed (bad RENDEZVOUS2 message)")]
283 RendezvousCompletionHandshake {
284 #[source]
286 error: tor_proto::Error,
287
288 intro_index: IntroPtIndex,
290
291 rend_pt: RendPtIdentityForError,
294 },
295
296 #[error("{0}")]
298 Bug(#[from] Bug),
299}
300define_asref_dyn_std_error!(FailedAttemptError);
301
302impl FailedAttemptError {
303 pub(crate) fn intro_index(&self) -> Option<IntroPtIndex> {
309 use FailedAttemptError as FAE;
310 match self {
311 FAE::UnusableIntro { intro_index, .. }
312 | FAE::RendezvousCompletionCircuitError { intro_index, .. }
313 | FAE::RendezvousCompletionHandshake { intro_index, .. }
314 | FAE::RendezvousCompletionTimeout { intro_index, .. }
315 | FAE::IntroductionCircuitObtain { intro_index, .. }
316 | FAE::IntroductionExchange { intro_index, .. }
317 | FAE::IntroductionFailed { intro_index, .. }
318 | FAE::IntroductionTimeout { intro_index, .. } => Some(*intro_index),
319 FAE::RendezvousCircuitObtain { .. }
320 | FAE::RendezvousEstablish { .. }
321 | FAE::RendezvousEstablishTimeout { .. }
322 | FAE::Bug(_) => None,
323 }
324 }
325}
326
327impl HasRetryTime for FailedAttemptError {
345 fn retry_time(&self) -> RetryTime {
346 use FailedAttemptError as FAE;
347 use RetryTime as RT;
348 match self {
349 FAE::UnusableIntro { error, .. } => error.retry_time(),
351 FAE::RendezvousCircuitObtain { error } => error.retry_time(),
352 FAE::IntroductionCircuitObtain { error, .. } => error.retry_time(),
353 FAE::IntroductionFailed { status, .. } => status.retry_time(),
354 FAE::RendezvousCompletionCircuitError { error: _e, .. }
356 | FAE::IntroductionExchange { error: _e, .. }
357 | FAE::RendezvousEstablish { error: _e, .. } => RT::AfterWaiting,
358 FAE::RendezvousEstablishTimeout { .. }
360 | FAE::RendezvousCompletionTimeout { .. }
361 | FAE::IntroductionTimeout { .. } => RT::AfterWaiting,
362 FAE::RendezvousCompletionHandshake { error: _e, .. } => RT::Never,
365 FAE::Bug(_) => RT::Never,
366 }
367 }
368}
369
370impl HasKind for ConnError {
371 fn kind(&self) -> ErrorKind {
372 use ConnError as CE;
373 use ErrorKind as EK;
374 match self {
375 CE::InvalidHsId => EK::InvalidStreamTarget,
376 CE::NoHsDirs => EK::TorDirectoryUnusable,
377 CE::NoUsableIntroPoints => EK::OnionServiceProtocolViolation,
378 CE::Spawn { cause, .. } => cause.kind(),
379 CE::Bug(e) => e.kind(),
380
381 CE::DescriptorDownload(attempts) => attempts
382 .sources()
383 .max_by_key(|attempt| DescriptorErrorDetailDiscriminants::from(&attempt.0.error))
384 .map(|attempt| attempt.0.kind())
385 .unwrap_or_else(|| {
386 let bug = internal!("internal error, empty CE::DescriptorDownload");
387 error!("bug: {}", bug.report());
388 bug.kind()
389 }),
390
391 CE::NoUsableHsDirs => EK::OnionServiceConnectionFailed,
392
393 CE::Failed(attempts) => attempts
394 .sources()
395 .max_by_key(|attempt| FailedAttemptErrorDiscriminants::from(*attempt))
396 .map(|attempt| attempt.kind())
397 .unwrap_or_else(|| {
398 let bug = internal!("internal error, empty CE::DescriptorDownload");
399 error!("bug: {}", bug.report());
400 bug.kind()
401 }),
402 }
403 }
404}
405
406impl HasKind for DescriptorError {
407 fn kind(&self) -> ErrorKind {
408 self.error.kind()
409 }
410}
411
412impl HasKind for DescriptorErrorDetail {
413 fn kind(&self) -> ErrorKind {
414 use DescriptorErrorDetail as DED;
415 use ErrorKind as EK;
416 use tor_dirclient::RequestError as RE;
417 match self {
418 DED::Timeout => EK::TorNetworkTimeout,
419 DED::Circuit(e) => e.kind(),
420 DED::Stream(e) => e.kind(),
421 DED::Directory(RE::HttpStatus(st, _)) if *st == 404 => EK::OnionServiceNotFound,
422 DED::Directory(RE::ResponseTooLong(_)) => EK::OnionServiceProtocolViolation,
423 DED::Directory(RE::HeadersTooLong(_)) => EK::OnionServiceProtocolViolation,
424 DED::Directory(RE::Utf8Encoding(_)) => EK::OnionServiceProtocolViolation,
425 DED::Directory(other_re) => other_re.kind(),
426 DED::OutsideValidityPeriod(_) => EK::OnionServiceProtocolViolation,
427 DED::ParameterMismatch(_) => EK::OnionServiceProtocolViolation,
428 DED::Descriptor(e) => e.kind(),
429 DED::Bug(e) => e.kind(),
430 }
431 }
432}
433
434impl HasKind for FailedAttemptError {
435 fn kind(&self) -> ErrorKind {
436 use ErrorKind as EK;
440 use FailedAttemptError as FAE;
441 match self {
442 FAE::UnusableIntro { .. } => EK::OnionServiceProtocolViolation,
443 FAE::RendezvousCircuitObtain { error, .. } => error.kind(),
444 FAE::RendezvousEstablish { error, .. } => error.kind(),
445 FAE::RendezvousCompletionCircuitError { error, .. } => error.kind(),
446 FAE::RendezvousCompletionHandshake { error, .. } => error.kind(),
447 FAE::RendezvousEstablishTimeout { .. } => EK::TorNetworkTimeout,
448 FAE::IntroductionCircuitObtain { error, .. } => error.kind(),
449 FAE::IntroductionExchange { error, .. } => error.kind(),
450 FAE::IntroductionFailed { .. } => EK::OnionServiceConnectionFailed,
451 FAE::IntroductionTimeout { .. } => EK::TorNetworkTimeout,
452 FAE::RendezvousCompletionTimeout { .. } => EK::RemoteNetworkTimeout,
453 FAE::Bug(e) => e.kind(),
454 }
455 }
456}
457
458#[derive(Error, Clone, Debug)]
460#[non_exhaustive]
461pub enum StartupError {
462 #[error("Unable to spawn {spawning}")]
464 Spawn {
465 spawning: &'static str,
467 #[source]
469 cause: Arc<SpawnError>,
470 },
471
472 #[error("{0}")]
474 Bug(#[from] Bug),
475}
476
477impl HasKind for StartupError {
478 fn kind(&self) -> ErrorKind {
479 use StartupError as SE;
480 match self {
481 SE::Spawn { cause, .. } => cause.kind(),
482 SE::Bug(e) => e.kind(),
483 }
484 }
485}
486
487#[derive(Error, Clone, Debug)]
493#[non_exhaustive]
494pub(crate) enum ProofOfWorkError {
495 #[error("Runtime error from client puzzle solver, #{0}")]
497 Runtime(#[from] tor_hscrypto::pow::RuntimeError),
498
499 #[error("Client puzzle parameters are not valid at this time")]
501 TimeValidity(#[from] tor_checkable::TimeValidityError),
502
503 #[error("Unexpectedly lost contact with solver task")]
505 #[allow(dead_code)]
506 SolverDisconnected,
507}
508
509impl DescriptorErrorDetail {
510 pub(crate) fn should_report_as_suspicious(&self) -> bool {
513 use DescriptorErrorDetail as E;
514 match self {
515 E::Timeout => false,
516 E::Circuit(_) => false,
517 E::Stream(_) => false, E::ParameterMismatch(_) => false,
519 E::Directory(e) => e.should_report_as_suspicious_if_anon(),
520 E::Descriptor(e) => e.should_report_as_suspicious(),
521 E::OutsideValidityPeriod(_) => false,
522 E::Bug(_) => false,
523 }
524 }
525}