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::{internal, Bug, ErrorKind, ErrorReport as _, HasKind, HasRetryTime, RetryTime};
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    /// Unable to spawn
66    #[error("Unable to spawn {spawning}")]
67    Spawn {
68        /// What we were trying to spawn
69        spawning: &'static str,
70        /// What happened when we tried to spawn it
71        #[source]
72        cause: Arc<SpawnError>,
73    },
74
75    /// Internal error
76    #[error("{0}")]
77    Bug(#[from] Bug),
78}
79
80/// Error that occurred attempting to download a descriptor
81#[derive(Error, Clone, Debug)]
82#[non_exhaustive]
83#[error("tried hsdir {hsdir}: {error}")]
84pub struct DescriptorError {
85    /// Which hsdir we were trying
86    // TODO #813 this should be Redacted<RelayDescription> or something
87    // It seems likely that the set of redacted hsdir ids could identify the service,
88    // so use Sensitive rather than Redacted.
89    pub hsdir: Sensitive<Ed25519Identity>,
90
91    /// What happened
92    #[source]
93    pub error: DescriptorErrorDetail,
94}
95define_asref_dyn_std_error!(DescriptorError);
96
97/// Error that occurred attempting to download a descriptor
98#[derive(Error, Clone, Debug)]
99#[non_exhaustive]
100//
101// NOTE! These are in an order!  "Most interesting" errors come last.
102// Specifically, after various attempts, the ErrorKind of the overall error
103// will be that of the error which is latest in this enum.
104//
105#[derive(strum::EnumDiscriminants)]
106#[strum_discriminants(derive(PartialOrd, Ord))]
107pub enum DescriptorErrorDetail {
108    /// Timed out
109    #[error("timed out")]
110    Timeout,
111
112    /// Failed to establish circuit to hidden service directory
113    #[error("circuit failed")]
114    Circuit(#[from] tor_circmgr::Error),
115
116    /// Failed to establish stream to hidden service directory
117    #[error("stream failed")]
118    Stream(#[source] tor_proto::Error),
119
120    /// Failed to make directory request
121    #[error("directory error")]
122    Directory(#[from] tor_dirclient::RequestError),
123
124    /// Failed to parse or validate descriptor
125    #[error("problem with descriptor")]
126    Descriptor(#[from] tor_netdoc::doc::hsdesc::HsDescError),
127
128    /// Internal error
129    #[error("{0}")]
130    Bug(#[from] Bug),
131}
132
133/// Error that occurred making one attempt to connect to a hidden service using an IP and RP
134#[derive(Error, Clone, Debug)]
135#[non_exhaustive]
136//
137// NOTE! These are in an order!  "Most interesting" errors come last.
138// Specifically, after various attempts, the ErrorKind of the overall error
139// will be that of the error which is latest in this enum.
140//
141#[derive(strum::EnumDiscriminants)]
142#[strum_discriminants(derive(PartialOrd, Ord))]
143// TODO HS is this the right name for this type?  It's a very mixed bag, so maybe it is.
144pub enum FailedAttemptError {
145    /// Introduction point unusable because it couldn't be used as a circuit target
146    #[error("Unusable introduction point #{intro_index}")]
147    UnusableIntro {
148        /// Why it's not use able
149        #[source]
150        error: crate::relay_info::InvalidTarget,
151
152        /// The index of the IPT in the list of IPTs in the descriptor
153        intro_index: IntroPtIndex,
154    },
155
156    /// Failed to obtain any circuit to use as a rendezvous circuit
157    #[error("Failed to obtain any circuit to use as a rendezvous circuit")]
158    RendezvousCircuitObtain {
159        /// Why it's not use able
160        #[source]
161        error: tor_circmgr::Error,
162    },
163
164    /// Creating a rendezvous circuit and rendezvous point took too long
165    #[error("Creating a rendezvous circuit and rendezvous point took too long")]
166    RendezvousEstablishTimeout {
167        /// Which relay did we choose for rendezvous point
168        // TODO #813 this should be Redacted<RelayDescription> or something
169        rend_pt: RendPtIdentityForError,
170    },
171
172    /// Failed to establish rendezvous point
173    #[error("Failed to establish rendezvous point at {rend_pt}")]
174    RendezvousEstablish {
175        /// What happened
176        #[source]
177        error: tor_proto::Error,
178
179        /// Which relay did we choose for rendezvous point
180        // TODO #813 this should be Redacted<RelayDescription> or something
181        rend_pt: RendPtIdentityForError,
182    },
183
184    /// Failed to obtain circuit to introduction point
185    #[error("Failed to obtain circuit to introduction point {intro_index}")]
186    IntroductionCircuitObtain {
187        /// What happened
188        #[source]
189        error: tor_circmgr::Error,
190
191        /// The index of the IPT in the list of IPTs in the descriptor
192        intro_index: IntroPtIndex,
193    },
194
195    /// Introduction exchange (with the introduction point) failed
196    #[error("Introduction exchange (with the introduction point) failed")]
197    IntroductionExchange {
198        /// What happened
199        #[source]
200        error: tor_proto::Error,
201
202        /// The index of the IPT in the list of IPTs in the descriptor
203        intro_index: IntroPtIndex,
204    },
205
206    /// Introduction point reported error in its INTRODUCE_ACK
207    #[error("Introduction point reported error in its INTRODUCE_ACK: {status}")]
208    IntroductionFailed {
209        /// The status code provided by the introduction point
210        status: IntroduceAckStatus,
211
212        /// The index of the IPT in the list of IPTs in the descriptor
213        intro_index: IntroPtIndex,
214    },
215
216    /// Communication with introduction point {intro_index} took too long
217    ///
218    /// This might mean it took too long to establish a circuit to the IPT,
219    /// or that the INTRODUCE exchange took too long.
220    #[error("Communication with introduction point {intro_index} took too long")]
221    IntroductionTimeout {
222        /// The index of the IPT in the list of IPTs in the descriptor
223        intro_index: IntroPtIndex,
224    },
225
226    /// It took too long for the rendezvous to be completed
227    ///
228    /// This might be the fault of almost anyone.  All we know is that we got
229    /// a successful `INTRODUCE_ACK` but the `RENDEZVOUS2` never arrived.
230    #[error("Rendezvous at {rend_pt} using introduction point {intro_index} took too long")]
231    RendezvousCompletionTimeout {
232        /// The index of the IPT in the list of IPTs in the descriptor
233        intro_index: IntroPtIndex,
234
235        /// Which relay did we choose for rendezvous point
236        // TODO #813 this should be Redacted<RelayDescription> or something
237        rend_pt: RendPtIdentityForError,
238    },
239
240    /// Error on rendezvous circuit when expecting rendezvous completion (`RENDEZVOUS2`)
241    #[error(
242        "Error on rendezvous circuit when expecting rendezvous completion (RENDEZVOUS2 message)"
243    )]
244    RendezvousCompletionCircuitError {
245        /// What happened
246        #[source]
247        error: tor_proto::Error,
248
249        /// The index of the IPT in the list of IPTs in the descriptor
250        intro_index: IntroPtIndex,
251
252        /// Which relay did we choose for rendezvous point
253        // TODO #813 this should be Redacted<RelayDescription> or something
254        rend_pt: RendPtIdentityForError,
255    },
256
257    /// Error processing rendezvous completion (`RENDEZVOUS2`)
258    ///
259    /// This is might be the fault of the hidden service or the rendezvous point.
260    #[error("Rendezvous completion end-to-end crypto handshake failed (bad RENDEZVOUS2 message)")]
261    RendezvousCompletionHandshake {
262        /// What happened
263        #[source]
264        error: tor_proto::Error,
265
266        /// The index of the IPT in the list of IPTs in the descriptor
267        intro_index: IntroPtIndex,
268
269        /// Which relay did we choose for rendezvous point
270        // TODO #813 this should be Redacted<RelayDescription> or something
271        rend_pt: RendPtIdentityForError,
272    },
273
274    /// Internal error
275    #[error("{0}")]
276    Bug(#[from] Bug),
277}
278define_asref_dyn_std_error!(FailedAttemptError);
279
280impl FailedAttemptError {
281    /// Which introduction point did this error involve (or implicate), if any?
282    ///
283    /// This is an index into the table in the HS descriptor,
284    /// so it can be less-than-useful outside the context where this error was generated.
285    // TODO derive this, too much human error possibility
286    pub(crate) fn intro_index(&self) -> Option<IntroPtIndex> {
287        use FailedAttemptError as FAE;
288        match self {
289            FAE::UnusableIntro { intro_index, .. }
290            | FAE::RendezvousCompletionCircuitError { intro_index, .. }
291            | FAE::RendezvousCompletionHandshake { intro_index, .. }
292            | FAE::RendezvousCompletionTimeout { intro_index, .. }
293            | FAE::IntroductionCircuitObtain { intro_index, .. }
294            | FAE::IntroductionExchange { intro_index, .. }
295            | FAE::IntroductionFailed { intro_index, .. }
296            | FAE::IntroductionTimeout { intro_index, .. } => Some(*intro_index),
297            FAE::RendezvousCircuitObtain { .. }
298            | FAE::RendezvousEstablish { .. }
299            | FAE::RendezvousEstablishTimeout { .. }
300            | FAE::Bug(_) => None,
301        }
302    }
303}
304
305/// When *an attempt like this* should be retried.
306///
307/// For error variants with an introduction point index
308/// (`FailedAttemptError::intro_index` returns `Some`)
309/// that's when we might retry *with that introduction point*.
310///
311/// For error variants with a rendezvous point,
312/// that's when we might retry *with that rendezvous point*.
313///
314/// For variants with both, we don't know
315/// which of the introduction point or rendezvous point is implicated.
316/// Retrying earlier with *one* different relay out of the two relays would be reasonable,
317/// as would delaying retrying with *either* of the same relays.
318//
319// Our current code doesn't keep history about rendezvous points.
320// We use this to choose what order to try the service's introduction points.
321// See `IptSortKey` in connect.rs.
322impl HasRetryTime for FailedAttemptError {
323    fn retry_time(&self) -> RetryTime {
324        use FailedAttemptError as FAE;
325        use RetryTime as RT;
326        match self {
327            // Delegate to the cause
328            FAE::UnusableIntro { error, .. } => error.retry_time(),
329            FAE::RendezvousCircuitObtain { error } => error.retry_time(),
330            FAE::IntroductionCircuitObtain { error, .. } => error.retry_time(),
331            FAE::IntroductionFailed { status, .. } => status.retry_time(),
332            // tor_proto::Error doesn't impl HasRetryTime, so we guess
333            FAE::RendezvousCompletionCircuitError { error: _e, .. }
334            | FAE::IntroductionExchange { error: _e, .. }
335            | FAE::RendezvousEstablish { error: _e, .. } => RT::AfterWaiting,
336            // Timeouts
337            FAE::RendezvousEstablishTimeout { .. }
338            | FAE::RendezvousCompletionTimeout { .. }
339            | FAE::IntroductionTimeout { .. } => RT::AfterWaiting,
340            // Other cases where we define the ErrorKind ourselves
341            // If service didn't cause this, it was the RPT, so prefer to try another RPT
342            FAE::RendezvousCompletionHandshake { error: _e, .. } => RT::Never,
343            FAE::Bug(_) => RT::Never,
344        }
345    }
346}
347
348impl HasKind for ConnError {
349    fn kind(&self) -> ErrorKind {
350        use ConnError as CE;
351        use ErrorKind as EK;
352        match self {
353            CE::InvalidHsId => EK::InvalidStreamTarget,
354            CE::NoHsDirs => EK::TorDirectoryUnusable,
355            CE::NoUsableIntroPoints => EK::OnionServiceProtocolViolation,
356            CE::Spawn { cause, .. } => cause.kind(),
357            CE::Bug(e) => e.kind(),
358
359            CE::DescriptorDownload(attempts) => attempts
360                .sources()
361                .max_by_key(|attempt| DescriptorErrorDetailDiscriminants::from(&attempt.0.error))
362                .map(|attempt| attempt.0.kind())
363                .unwrap_or_else(|| {
364                    let bug = internal!("internal error, empty CE::DescriptorDownload");
365                    error!("bug: {}", bug.report());
366                    bug.kind()
367                }),
368
369            CE::Failed(attempts) => attempts
370                .sources()
371                .max_by_key(|attempt| FailedAttemptErrorDiscriminants::from(*attempt))
372                .map(|attempt| attempt.kind())
373                .unwrap_or_else(|| {
374                    let bug = internal!("internal error, empty CE::DescriptorDownload");
375                    error!("bug: {}", bug.report());
376                    bug.kind()
377                }),
378        }
379    }
380}
381
382impl HasKind for DescriptorError {
383    fn kind(&self) -> ErrorKind {
384        self.error.kind()
385    }
386}
387
388impl HasKind for DescriptorErrorDetail {
389    fn kind(&self) -> ErrorKind {
390        use tor_dirclient::RequestError as RE;
391        use DescriptorErrorDetail as DED;
392        use ErrorKind as EK;
393        match self {
394            DED::Timeout => EK::TorNetworkTimeout,
395            DED::Circuit(e) => e.kind(),
396            DED::Stream(e) => e.kind(),
397            DED::Directory(RE::HttpStatus(st, _)) if *st == 404 => EK::OnionServiceNotFound,
398            DED::Directory(RE::ResponseTooLong(_)) => EK::OnionServiceProtocolViolation,
399            DED::Directory(RE::Utf8Encoding(_)) => EK::OnionServiceProtocolViolation,
400            DED::Directory(other_re) => other_re.kind(),
401            DED::Descriptor(e) => e.kind(),
402            DED::Bug(e) => e.kind(),
403        }
404    }
405}
406
407impl HasKind for FailedAttemptError {
408    fn kind(&self) -> ErrorKind {
409        /*use tor_dirclient::RequestError as RE;
410        use tor_netdoc::NetdocErrorKind as NEK;
411        use DescriptorErrorDetail as DED;*/
412        use ErrorKind as EK;
413        use FailedAttemptError as FAE;
414        match self {
415            FAE::UnusableIntro { .. } => EK::OnionServiceProtocolViolation,
416            FAE::RendezvousCircuitObtain { error, .. } => error.kind(),
417            FAE::RendezvousEstablish { error, .. } => error.kind(),
418            FAE::RendezvousCompletionCircuitError { error, .. } => error.kind(),
419            FAE::RendezvousCompletionHandshake { error, .. } => error.kind(),
420            FAE::RendezvousEstablishTimeout { .. } => EK::TorNetworkTimeout,
421            FAE::IntroductionCircuitObtain { error, .. } => error.kind(),
422            FAE::IntroductionExchange { error, .. } => error.kind(),
423            FAE::IntroductionFailed { .. } => EK::OnionServiceConnectionFailed,
424            FAE::IntroductionTimeout { .. } => EK::TorNetworkTimeout,
425            FAE::RendezvousCompletionTimeout { .. } => EK::RemoteNetworkTimeout,
426            FAE::Bug(e) => e.kind(),
427        }
428    }
429}
430
431/// Error that occurred attempting to start up a hidden service client connector
432#[derive(Error, Clone, Debug)]
433#[non_exhaustive]
434pub enum StartupError {
435    /// Unable to spawn
436    #[error("Unable to spawn {spawning}")]
437    Spawn {
438        /// What we were trying to spawn
439        spawning: &'static str,
440        /// What happened when we tried to spawn it
441        #[source]
442        cause: Arc<SpawnError>,
443    },
444
445    /// Internal error
446    #[error("{0}")]
447    Bug(#[from] Bug),
448}
449
450impl HasKind for StartupError {
451    fn kind(&self) -> ErrorKind {
452        use StartupError as SE;
453        match self {
454            SE::Spawn { cause, .. } => cause.kind(),
455            SE::Bug(e) => e.kind(),
456        }
457    }
458}
459
460/// Error that occurred while trying to solve a proof of work puzzle
461///
462/// These errors will not prevent a connection from proceeding.
463/// We may try a different proof of work scheme or none at all.
464///
465#[derive(Error, Clone, Debug)]
466#[non_exhaustive]
467pub(crate) enum ProofOfWorkError {
468    /// Runtime error from a specific proof of work scheme
469    #[error("Runtime error from client puzzle solver, #{0}")]
470    Runtime(#[from] tor_hscrypto::pow::RuntimeError),
471
472    /// Time-limited parameters are not valid
473    #[error("Client puzzle parameters are not valid at this time")]
474    TimeValidity(#[from] tor_checkable::TimeValidityError),
475
476    /// Unexpectedly lost contact with solver
477    #[error("Unexpectedly lost contact with solver task")]
478    #[allow(dead_code)]
479    SolverDisconnected,
480}