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::{internal, Bug, ErrorKind, ErrorReport as _, HasKind, HasRetryTime, RetryTime};
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
pub(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(fmt = "#{}", self + 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
    /// 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}")]
84
pub 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
}
95
define_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))]
107
pub 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.
144
pub 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
}
278
define_asref_dyn_std_error!(FailedAttemptError);
279

            
280
impl 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.
322
impl 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

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

            
382
impl HasKind for DescriptorError {
383
    fn kind(&self) -> ErrorKind {
384
        self.error.kind()
385
    }
386
}
387

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

            
407
impl 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]
434
pub 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

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