1
//! Errors relating to being a hidden service client
2
use std::sync::Arc;
3

            
4
use derive_more::{From, Into};
5
use futures::task::SpawnError;
6

            
7
use thiserror::Error;
8
use tracing::error;
9

            
10
use retry_error::RetryError;
11
use safelog::{Redacted, Sensitive};
12
use tor_cell::relaycell::hs::IntroduceAckStatus;
13
use tor_error::define_asref_dyn_std_error;
14
use tor_error::{Bug, ErrorKind, ErrorReport as _, HasKind, HasRetryTime, RetryTime, internal};
15
use tor_linkspec::RelayIds;
16
use tor_llcrypto::pk::ed25519::Ed25519Identity;
17
use tor_netdir::Relay;
18

            
19
/// Identity of a rendezvous point, for use in error reports
20
pub(crate) type RendPtIdentityForError = Redacted<RelayIds>;
21

            
22
/// Given a `Relay` for a rendezvous pt, provides its identify for use in error reports
23
518
pub(crate) fn rend_pt_identity_for_error(relay: &Relay<'_>) -> RendPtIdentityForError {
24
518
    RelayIds::from_relay_ids(relay).into()
25
518
}
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)]
36
pub struct IntroPtIndex(pub usize);
37

            
38
/// Error that occurred attempting to reach a hidden service
39
#[derive(Error, Clone, Debug)]
40
#[non_exhaustive]
41
pub 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}")]
92
pub 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
}
103
define_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))]
115
pub 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

            
149
impl 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.
166
pub 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
}
300
define_asref_dyn_std_error!(FailedAttemptError);
301

            
302
impl 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
166
    pub(crate) fn intro_index(&self) -> Option<IntroPtIndex> {
309
        use FailedAttemptError as FAE;
310
166
        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
166
            | FAE::IntroductionFailed { intro_index, .. }
318
166
            | FAE::IntroductionTimeout { intro_index, .. } => Some(*intro_index),
319
            FAE::RendezvousCircuitObtain { .. }
320
            | FAE::RendezvousEstablish { .. }
321
            | FAE::RendezvousEstablishTimeout { .. }
322
            | FAE::Bug(_) => None,
323
        }
324
166
    }
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.
344
impl HasRetryTime for FailedAttemptError {
345
166
    fn retry_time(&self) -> RetryTime {
346
        use FailedAttemptError as FAE;
347
        use RetryTime as RT;
348
166
        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
166
            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
166
    }
368
}
369

            
370
impl 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

            
406
impl HasKind for DescriptorError {
407
    fn kind(&self) -> ErrorKind {
408
        self.error.kind()
409
    }
410
}
411

            
412
impl 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

            
434
impl HasKind for FailedAttemptError {
435
166
    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
166
        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
166
            FAE::IntroductionFailed { .. } => EK::OnionServiceConnectionFailed,
451
            FAE::IntroductionTimeout { .. } => EK::TorNetworkTimeout,
452
            FAE::RendezvousCompletionTimeout { .. } => EK::RemoteNetworkTimeout,
453
            FAE::Bug(e) => e.kind(),
454
        }
455
166
    }
456
}
457

            
458
/// Error that occurred attempting to start up a hidden service client connector
459
#[derive(Error, Clone, Debug)]
460
#[non_exhaustive]
461
pub 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

            
477
impl 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]
494
pub(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

            
509
impl 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
}