Skip to main content

tor_hsclient/
err.rs

1//! Errors relating to being a hidden service client
2use 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
19/// Identity of a rendezvous point, for use in error reports
20pub(crate) type RendPtIdentityForError = Redacted<RelayIds>;
21
22/// Given a `Relay` for a rendezvous pt, provides its identify for use in error reports
23pub(crate) fn rend_pt_identity_for_error(relay: &Relay<'_>) -> RendPtIdentityForError {
24    RelayIds::from_relay_ids(relay).into()
25}
26
27/// Index of an introduction point in the descriptor
28///
29/// Principally used in error reporting.
30///
31/// Formats as `#<n+1>`.
32#[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/// Error that occurred attempting to reach a hidden service
39#[derive(Error, Clone, Debug)]
40#[non_exhaustive]
41pub enum ConnError {
42    /// Invalid hidden service identity (`.onion` address)
43    #[error("Invalid hidden service identity (`.onion` address)")]
44    InvalidHsId,
45
46    /// Unable to download hidden service descriptor
47    #[error("Unable to download hidden service descriptor")]
48    DescriptorDownload(RetryError<tor_error::Report<DescriptorError>>),
49
50    /// Obtained descriptor but unable to connect to hidden service due to problem with IPT or RPT
51    // TODO HS is this the right name for this variant?
52    #[error("Unable to connect to hidden service using any Rendezvous Point / Introduction Point")]
53    Failed(#[source] RetryError<FailedAttemptError>),
54
55    /// The consensus network contains no suitable hidden service directories!
56    #[error("consensus contains no suitable hidden service directories")]
57    NoHsDirs,
58
59    /// The descriptor contained only unusable introduction points!
60    ///
61    /// This is the fault of the service, or shows incompatibility between us and them.
62    #[error("hidden service has no introduction points usable by us")]
63    NoUsableIntroPoints,
64
65    /// Cannot fetch the descriptor because we are rate-limited
66    ///
67    /// Returned if we do not have a descriptor for the service,
68    /// and we cannot download a new one because a rate-limit
69    /// is in place for all HsDirs.
70    #[error("cannot fetch descriptor (we are rate-limited)")]
71    NoUsableHsDirs,
72
73    /// Unable to spawn
74    #[error("Unable to spawn {spawning}")]
75    Spawn {
76        /// What we were trying to spawn
77        spawning: &'static str,
78        /// What happened when we tried to spawn it
79        #[source]
80        cause: Arc<SpawnError>,
81    },
82
83    /// Internal error
84    #[error("{0}")]
85    Bug(#[from] Bug),
86}
87
88/// Error that occurred attempting to download a descriptor
89#[derive(Error, Clone, Debug)]
90#[non_exhaustive]
91#[error("tried hsdir {hsdir}: {error}")]
92pub struct DescriptorError {
93    /// Which hsdir we were trying
94    // TODO #813 this should be Redacted<RelayDescription> or something
95    // It seems likely that the set of redacted hsdir ids could identify the service,
96    // so use Sensitive rather than Redacted.
97    pub hsdir: Sensitive<Ed25519Identity>,
98
99    /// What happened
100    #[source]
101    pub error: DescriptorErrorDetail,
102}
103define_asref_dyn_std_error!(DescriptorError);
104
105/// Error that occurred attempting to download a descriptor
106#[derive(Error, Clone, Debug)]
107#[non_exhaustive]
108//
109// NOTE! These are in an order!  "Most interesting" errors come last.
110// Specifically, after various attempts, the ErrorKind of the overall error
111// will be that of the error which is latest in this enum.
112//
113#[derive(strum::EnumDiscriminants)]
114#[strum_discriminants(derive(PartialOrd, Ord))]
115pub enum DescriptorErrorDetail {
116    /// Timed out
117    #[error("timed out")]
118    Timeout,
119
120    /// Failed to establish circuit to hidden service directory
121    #[error("circuit failed")]
122    Circuit(#[from] tor_circmgr::Error),
123
124    /// Failed to establish stream to hidden service directory
125    #[error("stream failed")]
126    Stream(#[source] tor_proto::Error),
127
128    /// Failed to make directory request
129    #[error("directory error")]
130    Directory(#[from] tor_dirclient::RequestError),
131
132    /// Incompatibility with network parameters
133    #[error("descriptor was not compatible with network parameters: {0}")]
134    ParameterMismatch(String),
135
136    /// Descriptor is outside its validity period
137    #[error("descriptor is outside its validity period")]
138    OutsideValidityPeriod(#[from] tor_checkable::TimeValidityError),
139
140    /// Failed to parse or validate descriptor
141    #[error("problem with descriptor")]
142    Descriptor(#[from] tor_netdoc::doc::hsdesc::HsDescError),
143
144    /// Internal error
145    #[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/// Error that occurred making one attempt to connect to a hidden service using an IP and RP
156#[derive(Error, Clone, Debug)]
157#[non_exhaustive]
158//
159// NOTE! These are in an order!  "Most interesting" errors come last.
160// Specifically, after various attempts, the ErrorKind of the overall error
161// will be that of the error which is latest in this enum.
162//
163#[derive(strum::EnumDiscriminants)]
164#[strum_discriminants(derive(PartialOrd, Ord))]
165// TODO HS is this the right name for this type?  It's a very mixed bag, so maybe it is.
166pub enum FailedAttemptError {
167    /// Introduction point unusable because it couldn't be used as a circuit target
168    #[error("Unusable introduction point #{intro_index}")]
169    UnusableIntro {
170        /// Why it's not use able
171        #[source]
172        error: crate::relay_info::InvalidTarget,
173
174        /// The index of the IPT in the list of IPTs in the descriptor
175        intro_index: IntroPtIndex,
176    },
177
178    /// Failed to obtain any circuit to use as a rendezvous circuit
179    #[error("Failed to obtain any circuit to use as a rendezvous circuit")]
180    RendezvousCircuitObtain {
181        /// Why it's not use able
182        #[source]
183        error: tor_circmgr::Error,
184    },
185
186    /// Creating a rendezvous circuit and rendezvous point took too long
187    #[error("Creating a rendezvous circuit and rendezvous point took too long")]
188    RendezvousEstablishTimeout {
189        /// Which relay did we choose for rendezvous point
190        // TODO #813 this should be Redacted<RelayDescription> or something
191        rend_pt: RendPtIdentityForError,
192    },
193
194    /// Failed to establish rendezvous point
195    #[error("Failed to establish rendezvous point at {rend_pt}")]
196    RendezvousEstablish {
197        /// What happened
198        #[source]
199        error: tor_proto::Error,
200
201        /// Which relay did we choose for rendezvous point
202        // TODO #813 this should be Redacted<RelayDescription> or something
203        rend_pt: RendPtIdentityForError,
204    },
205
206    /// Failed to obtain circuit to introduction point
207    #[error("Failed to obtain circuit to introduction point {intro_index}")]
208    IntroductionCircuitObtain {
209        /// What happened
210        #[source]
211        error: tor_circmgr::Error,
212
213        /// The index of the IPT in the list of IPTs in the descriptor
214        intro_index: IntroPtIndex,
215    },
216
217    /// Introduction exchange (with the introduction point) failed
218    #[error("Introduction exchange (with the introduction point) failed")]
219    IntroductionExchange {
220        /// What happened
221        #[source]
222        error: tor_proto::Error,
223
224        /// The index of the IPT in the list of IPTs in the descriptor
225        intro_index: IntroPtIndex,
226    },
227
228    /// Introduction point reported error in its INTRODUCE_ACK
229    #[error("Introduction point {intro_index} reported error in its INTRODUCE_ACK: {status}")]
230    IntroductionFailed {
231        /// The status code provided by the introduction point
232        status: IntroduceAckStatus,
233
234        /// The index of the IPT in the list of IPTs in the descriptor
235        intro_index: IntroPtIndex,
236    },
237
238    /// Communication with introduction point {intro_index} took too long
239    ///
240    /// This might mean it took too long to establish a circuit to the IPT,
241    /// or that the INTRODUCE exchange took too long.
242    #[error("Communication with introduction point {intro_index} took too long")]
243    IntroductionTimeout {
244        /// The index of the IPT in the list of IPTs in the descriptor
245        intro_index: IntroPtIndex,
246    },
247
248    /// It took too long for the rendezvous to be completed
249    ///
250    /// This might be the fault of almost anyone.  All we know is that we got
251    /// a successful `INTRODUCE_ACK` but the `RENDEZVOUS2` never arrived.
252    #[error("Rendezvous at {rend_pt} using introduction point {intro_index} took too long")]
253    RendezvousCompletionTimeout {
254        /// The index of the IPT in the list of IPTs in the descriptor
255        intro_index: IntroPtIndex,
256
257        /// Which relay did we choose for rendezvous point
258        // TODO #813 this should be Redacted<RelayDescription> or something
259        rend_pt: RendPtIdentityForError,
260    },
261
262    /// Error on rendezvous circuit when expecting rendezvous completion (`RENDEZVOUS2`)
263    #[error(
264        "Error on rendezvous circuit when expecting rendezvous completion (RENDEZVOUS2 message)"
265    )]
266    RendezvousCompletionCircuitError {
267        /// What happened
268        #[source]
269        error: tor_proto::Error,
270
271        /// The index of the IPT in the list of IPTs in the descriptor
272        intro_index: IntroPtIndex,
273
274        /// Which relay did we choose for rendezvous point
275        // TODO #813 this should be Redacted<RelayDescription> or something
276        rend_pt: RendPtIdentityForError,
277    },
278
279    /// Error processing rendezvous completion (`RENDEZVOUS2`)
280    ///
281    /// This is might be the fault of the hidden service or the rendezvous point.
282    #[error("Rendezvous completion end-to-end crypto handshake failed (bad RENDEZVOUS2 message)")]
283    RendezvousCompletionHandshake {
284        /// What happened
285        #[source]
286        error: tor_proto::Error,
287
288        /// The index of the IPT in the list of IPTs in the descriptor
289        intro_index: IntroPtIndex,
290
291        /// Which relay did we choose for rendezvous point
292        // TODO #813 this should be Redacted<RelayDescription> or something
293        rend_pt: RendPtIdentityForError,
294    },
295
296    /// Internal error
297    #[error("{0}")]
298    Bug(#[from] Bug),
299}
300define_asref_dyn_std_error!(FailedAttemptError);
301
302impl FailedAttemptError {
303    /// Which introduction point did this error involve (or implicate), if any?
304    ///
305    /// This is an index into the table in the HS descriptor,
306    /// so it can be less-than-useful outside the context where this error was generated.
307    // TODO derive this, too much human error possibility
308    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
327/// When *an attempt like this* should be retried.
328///
329/// For error variants with an introduction point index
330/// (`FailedAttemptError::intro_index` returns `Some`)
331/// that's when we might retry *with that introduction point*.
332///
333/// For error variants with a rendezvous point,
334/// that's when we might retry *with that rendezvous point*.
335///
336/// For variants with both, we don't know
337/// which of the introduction point or rendezvous point is implicated.
338/// Retrying earlier with *one* different relay out of the two relays would be reasonable,
339/// as would delaying retrying with *either* of the same relays.
340//
341// Our current code doesn't keep history about rendezvous points.
342// We use this to choose what order to try the service's introduction points.
343// See `IptSortKey` in connect.rs.
344impl HasRetryTime for FailedAttemptError {
345    fn retry_time(&self) -> RetryTime {
346        use FailedAttemptError as FAE;
347        use RetryTime as RT;
348        match self {
349            // Delegate to the cause
350            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            // tor_proto::Error doesn't impl HasRetryTime, so we guess
355            FAE::RendezvousCompletionCircuitError { error: _e, .. }
356            | FAE::IntroductionExchange { error: _e, .. }
357            | FAE::RendezvousEstablish { error: _e, .. } => RT::AfterWaiting,
358            // Timeouts
359            FAE::RendezvousEstablishTimeout { .. }
360            | FAE::RendezvousCompletionTimeout { .. }
361            | FAE::IntroductionTimeout { .. } => RT::AfterWaiting,
362            // Other cases where we define the ErrorKind ourselves
363            // If service didn't cause this, it was the RPT, so prefer to try another RPT
364            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 tor_dirclient::RequestError as RE;
437        use tor_netdoc::NetdocErrorKind as NEK;
438        use DescriptorErrorDetail as DED;*/
439        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/// Error that occurred attempting to start up a hidden service client connector
459#[derive(Error, Clone, Debug)]
460#[non_exhaustive]
461pub enum StartupError {
462    /// Unable to spawn
463    #[error("Unable to spawn {spawning}")]
464    Spawn {
465        /// What we were trying to spawn
466        spawning: &'static str,
467        /// What happened when we tried to spawn it
468        #[source]
469        cause: Arc<SpawnError>,
470    },
471
472    /// Internal error
473    #[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/// Error that occurred while trying to solve a proof of work puzzle
488///
489/// These errors will not prevent a connection from proceeding.
490/// We may try a different proof of work scheme or none at all.
491///
492#[derive(Error, Clone, Debug)]
493#[non_exhaustive]
494pub(crate) enum ProofOfWorkError {
495    /// Runtime error from a specific proof of work scheme
496    #[error("Runtime error from client puzzle solver, #{0}")]
497    Runtime(#[from] tor_hscrypto::pow::RuntimeError),
498
499    /// Time-limited parameters are not valid
500    #[error("Client puzzle parameters are not valid at this time")]
501    TimeValidity(#[from] tor_checkable::TimeValidityError),
502
503    /// Unexpectedly lost contact with solver
504    #[error("Unexpectedly lost contact with solver task")]
505    #[allow(dead_code)]
506    SolverDisconnected,
507}
508
509impl DescriptorErrorDetail {
510    /// Return true if this error is one that we should report as a suspicious event,
511    /// along with the dirserver and description of the relevant document.
512    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, // TODO prop360
518            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}