Skip to main content

arti_client/
err.rs

1//! Declare tor client specific errors.
2
3mod hint;
4
5use std::fmt::{self, Display};
6use std::sync::Arc;
7
8use futures::task::SpawnError;
9
10#[cfg(feature = "onion-service-client")]
11use safelog::DisplayRedacted as _;
12use safelog::Sensitive;
13use thiserror::Error;
14use tor_circmgr::TargetPorts;
15use tor_error::{ErrorKind, HasKind};
16
17use crate::TorAddrError;
18#[cfg(feature = "onion-service-client")]
19use tor_hscrypto::pk::HsId;
20
21pub use hint::HintableError;
22
23/// Main high-level error type for the Arti Tor client
24///
25/// If you need to handle different types of errors differently, use the
26/// [`kind`](`tor_error::HasKind::kind`) trait method to check what kind of
27/// error it is.
28///
29/// Note that although this type implements that standard
30/// [`Error`](trait@std::error::Error) trait, the output of that trait's methods are
31/// not covered by semantic versioning.  Specifically: you should not rely on
32/// the specific output of `Display`, `Debug`, or `Error::source()` when run on
33/// this type; it may change between patch versions without notification.
34#[derive(Error, Clone, Debug)]
35pub struct Error {
36    /// The actual error.
37    ///
38    /// This field is exposed via the `detail()` method only if the
39    /// `error_detail` feature is enabled. Using it will void your semver
40    /// guarantee.
41    #[source]
42    detail: Box<ErrorDetail>,
43}
44
45impl From<ErrorDetail> for Error {
46    fn from(detail: ErrorDetail) -> Error {
47        Error {
48            detail: detail.into(),
49        }
50    }
51}
52
53/// Declare an enum as `pub` if `error_details` is enabled, and as `pub(crate)` otherwise.
54#[cfg(feature = "error_detail")]
55macro_rules! pub_if_error_detail {
56    {  $(#[$meta:meta])* enum $e:ident $tt:tt } => {
57        $(#[$meta])* pub enum $e $tt
58    }
59}
60
61/// Declare an enum as `pub` if `error_details` is enabled, and as `pub(crate)` otherwise.
62#[cfg(not(feature = "error_detail"))]
63macro_rules! pub_if_error_detail {
64    {  $(#[$meta:meta])* enum $e:ident $tt:tt } => {
65        $(#[$meta])* pub(crate) enum $e $tt }
66}
67
68// Hello, macro-fans!  There are some other solutions that we considered here
69// but didn't use.
70//
71// 1. For one, `pub_if_error_detail!{} enum ErrorDetail { ... }` would be neat,
72// but Rust doesn't allow macros to appear in that position.
73//
74// 2. We could also declare `ErrorDetail` here as `pub` unconditionally, and
75// rely on `mod err` being private to keep it out of the user's hands.  Then we
76// could conditionally re-export `ErrorDetail` in `lib`:
77//
78// ```
79// mod err {
80//    pub enum ErrorDetail { ... }
81// }
82//
83// #[cfg(feature = "error_detail")]
84// pub use err::ErrorDetail;
85// ```
86//
87// But if we did that, the compiler would no longer warn us if we
88// _unconditionally_ exposed the ErrorDetail type from somewhere else in this
89// crate.  That doesn't seem too safe.
90//
91// 3. At one point we had a macro more like:
92// ```
93// macro_rules! declare_error_detail { { $vis: $vis } } =>
94//  => { ... $vis enum ErrorDetail {...} }
95// ```
96// There's nothing wrong with that in principle, but it's no longer needed,
97// since we used to use $vis in several places but now it's only used in one.
98// Also, it's good to make macro declarations small, and rust-analyzer seems to
99// handle understand format a little bit better.
100
101pub_if_error_detail! {
102// We cheat with the indentation, a bit.  Happily rustfmt doesn't seem to mind.
103
104/// Represents errors that can occur while doing Tor operations.
105///
106/// This enumeration is the inner view of a
107/// [`arti_client::Error`](crate::Error): we don't expose it unless the
108/// `error_detail` feature is enabled.
109///
110/// The details of this enumeration are not stable: using the `error_detail`
111/// feature will void your semver guarantee.
112///
113/// Instead of looking at the type, you should try to use the
114/// [`kind`](`tor_error::HasKind::kind`) trait method to distinguish among
115/// different kinds of [`Error`](struct@crate::Error).  If that doesn't provide enough information
116/// for your use case, please let us know.
117#[cfg_attr(docsrs, doc(cfg(feature = "error_detail")))]
118#[cfg_attr(test, derive(strum::EnumDiscriminants))]
119#[cfg_attr(test, strum_discriminants(vis(pub(crate))))]
120#[derive(Error, Clone, Debug)]
121#[non_exhaustive]
122enum ErrorDetail {
123    /// Error setting up the memory quota tracker
124    #[error("Error setting up the memory quota tracker")]
125    MemquotaSetup(#[from] tor_memquota::StartupError),
126
127    /// Memory quota error while starting up Arti
128    #[error("Memory quota error during startup")]
129    MemquotaDuringStartup(#[from] tor_memquota::Error),
130
131    /// Error setting up the channel manager
132    // TODO: should "chanmgr setup error" be its own type in tor-chanmgr
133    #[error("Error setting up the channel manager")]
134    ChanMgrSetup(#[source] tor_chanmgr::Error),
135
136    /// Error setting up the guard manager
137    // TODO: should "guardmgr setup error" be its own type in tor-guardmgr?
138    #[error("Error setting up the guard manager")]
139    GuardMgrSetup(#[source] tor_guardmgr::GuardMgrError),
140
141    /// Error setting up the guard manager
142    // TODO: should "vanguardmgr setup error" be its own type in tor-guardmgr?
143    #[cfg(all(
144        feature = "vanguards",
145        any(feature = "onion-service-client", feature = "onion-service-service")
146    ))]
147    #[error("Error setting up the vanguard manager")]
148    VanguardMgrSetup(#[source] tor_guardmgr::VanguardMgrError),
149
150    /// Error setting up the circuit manager
151    // TODO: should "circmgr setup error" be its own type in tor-circmgr?
152    #[error("Error setting up the circuit manager")]
153    CircMgrSetup(#[source] tor_circmgr::Error),
154
155    /// Error setting up the bridge descriptor manager
156    #[error("Error setting up the bridge descriptor manager")]
157    #[cfg(feature = "bridge-client")]
158    BridgeDescMgrSetup(#[from] tor_dirmgr::bridgedesc::StartupError),
159
160    /// Error setting up the directory manager
161    // TODO: should "dirmgr setup error" be its own type in tor-dirmgr?
162    #[error("Error setting up the directory manager")]
163    DirMgrSetup(#[source] tor_dirmgr::Error),
164
165    /// Error setting up the state manager.
166    #[error("Error setting up the persistent state manager")]
167    StateMgrSetup(#[source] tor_persist::Error),
168
169    /// Error setting up the hidden service client connector.
170    #[error("Error setting up the hidden service client connector")]
171    #[cfg(feature = "onion-service-client")]
172    HsClientConnectorSetup(#[from] tor_hsclient::StartupError),
173
174    /// Error setting up onion service.
175    #[cfg(feature= "onion-service-service")]
176    #[error("Error setting up onion service")]
177    OnionServiceSetup(#[source] tor_hsservice::StartupError),
178
179    /// Failed to obtain exit circuit
180    #[error("Failed to obtain exit circuit for ports {exit_ports}")]
181    ObtainExitCircuit {
182        /// The ports that we wanted a circuit for.
183        exit_ports: Sensitive<TargetPorts>,
184
185        /// What went wrong
186        #[source]
187        cause: tor_circmgr::Error,
188    },
189
190    /// Failed to obtain hidden service circuit
191    #[cfg(feature = "onion-service-client")]
192    #[error("Failed to obtain hidden service circuit to {}", hsid.display_redacted())]
193    ObtainHsCircuit {
194        /// The service we were trying to connect to
195        hsid: HsId,
196
197        /// What went wrong
198        #[source]
199        cause: tor_hsclient::ConnError,
200    },
201
202    /// Directory manager was unable to bootstrap a working directory.
203    #[error("Unable to bootstrap a working directory")]
204    DirMgrBootstrap(#[source] tor_dirmgr::Error),
205
206    /// A protocol error while launching a stream
207    #[error("Protocol error while launching a {kind} stream")]
208    StreamFailed {
209        /// What kind of stream we were trying to launch.
210        kind: &'static str,
211
212        /// The error that occurred.
213        #[source]
214        cause: tor_circmgr::Error
215    },
216
217    /// An error while interfacing with the persistent data layer.
218    #[error("Error while trying to access persistent state")]
219    StateAccess(#[source] tor_persist::Error),
220
221    /// We asked an exit to do something, and waited too long for an answer.
222    #[error("Timed out while waiting for answer from exit")]
223    ExitTimeout,
224
225    /// Onion services are not compiled in, but we were asked to connect to one.
226    #[error("Rejecting .onion address; feature onion-service-client not compiled in")]
227    OnionAddressNotSupported,
228
229    /// Onion services are not enabled, but we were asked to connect to one.
230    ///
231    /// This error occurs when Arti is built with onion service support, but
232    /// onion services are disabled via our stream preferences.
233    ///
234    /// To enable onion services, set `allow_onion_addrs` to `true` in the
235    /// `address_filter` configuration section.  Alternatively, set
236    /// `connect_to_onion_services` in your `StreamPrefs` object.
237    #[cfg(feature = "onion-service-client")]
238    #[error("Rejecting .onion address; allow_onion_addrs disabled in stream preferences")]
239    OnionAddressDisabled,
240
241    /// Error when trying to find the IP address of a hidden service
242    #[error("A .onion address cannot be resolved to an IP address")]
243    OnionAddressResolveRequest,
244
245    /// Unusable target address.
246    ///
247    /// `TorAddrError::InvalidHostname` should not appear here;
248    /// use `ErrorDetail::InvalidHostname` instead.
249    // TODO this is a violation of the "make invalid states unrepresentable" principle,
250    // but maybe that doesn't matter too much here?
251    #[error("Could not parse target address")]
252    Address(crate::address::TorAddrError),
253
254    /// Hostname not valid.
255    #[error("Rejecting hostname as invalid")]
256    InvalidHostname,
257
258    /// Address was local, and we don't permit connecting to those over Tor.
259    #[error("Cannot connect to a local-only address without enabling allow_local_addrs")]
260    LocalAddress,
261
262    /// A domain name we were asked to resolve does not resolve to any routable addresses.
263    #[error("Cannot resolve a local-only address without enabling allow_resolving_local_addrs")]
264    NoRoutableAddress,
265
266    /// Building configuration for the client failed.
267    #[error("Problem with configuration")]
268    Configuration(#[from] tor_config::ConfigBuildError),
269
270    /// Unable to change configuration.
271    #[error("Unable to change configuration")]
272    Reconfigure(#[from] tor_config::ReconfigureError),
273
274    /// Problem creating or launching a pluggable transport.
275    #[cfg(feature="pt-client")]
276    #[error("Problem with a pluggable transport")]
277    PluggableTransport(#[from] tor_ptmgr::err::PtError),
278
279    /// We encountered a problem while inspecting or creating a directory.
280    #[error("Problem accessing filesystem")]
281    FsMistrust(#[from] fs_mistrust::Error),
282
283    /// Unable to spawn task
284    #[error("Unable to spawn {spawning}")]
285    Spawn {
286        /// What we were trying to spawn.
287        spawning: &'static str,
288        /// What happened when we tried to spawn it.
289        #[source]
290        cause: Arc<SpawnError>
291    },
292
293    /// Attempted to use an unbootstrapped `TorClient` for something that
294    /// requires bootstrapping to have completed.
295    #[error("Cannot {action} with unbootstrapped client")]
296    BootstrapRequired {
297        /// What we were trying to do that required bootstrapping.
298        action: &'static str
299    },
300
301    /// Attempted to use a `TorClient` for something when it did not
302    /// have a valid directory.
303    #[error("Tried to {action} without a valid directory")]
304    NoDir {
305        /// The underlying error.
306        #[source]
307        error: tor_netdir::Error,
308        /// What we were trying to do that needed a directory.
309        action: &'static str,
310    },
311
312    /// A key store access failed.
313    #[error("Error while trying to access a key store")]
314    Keystore(#[from] tor_keymgr::Error),
315
316    /// Attempted to use a `TorClient` for something that
317    /// requires the keystore to be enabled in the configuration.
318    #[error("Cannot {action} without enabling storage.keystore")]
319    KeystoreRequired {
320        /// What we were trying to do that required the keystore to be enabled.
321        action: &'static str
322    },
323
324    /// Encountered a malformed client specifier.
325    #[error("Bad client specifier")]
326    BadClientSpecifier(#[from] tor_keymgr::ArtiPathSyntaxError),
327
328    /// We tried to parse an onion address, but we found that it was invalid.
329    ///
330    /// This error occurs if we are asked to connect to an invalid .onion address.
331    #[cfg(feature = "onion-service-client")]
332    #[error("Invalid onion address")]
333    BadOnionAddress(#[from] tor_hscrypto::pk::HsIdParseError),
334
335    /// We were unable to launch an onion service, even though we
336    /// we are configured to be able to do so.
337    #[cfg(feature= "onion-service-service")]
338    #[error("Unable to launch onion service")]
339    LaunchOnionService(#[source] tor_hsservice::StartupError),
340
341    /// We found that at least one required protocol was missing.
342    #[error("Arti is missing a required protocol feature")]
343    MissingProtocol(#[source] tor_netdoc::doc::netstatus::ProtocolSupportError),
344
345    /// A programming problem, either in our code or the code calling it.
346    #[error("Programming problem")]
347    Bug(#[from] tor_error::Bug),
348}
349
350// End of the use of $vis to refer to visibility according to `error_detail`
351}
352
353#[cfg(feature = "error_detail")]
354impl Error {
355    /// Return the underlying error detail object for this error.
356    ///
357    /// In general, it's not a good idea to use this function.  Our
358    /// `arti_client::ErrorDetail` objects are unstable, and matching on them is
359    /// probably not the best way to achieve whatever you're trying to do.
360    /// Instead, we recommend using  the [`kind`](`tor_error::HasKind::kind`)
361    /// trait method if your program needs to distinguish among different types
362    /// of errors.
363    ///
364    /// (If the above function don't meet your needs, please let us know!)
365    ///
366    /// This function is only available when `arti-client` is built with the
367    /// `error_detail` feature.  Using this function will void your semver
368    /// guarantees.
369    pub fn detail(&self) -> &ErrorDetail {
370        &self.detail
371    }
372}
373
374impl Error {
375    /// Consume this error and return the underlying error detail object.
376    pub(crate) fn into_detail(self) -> ErrorDetail {
377        *self.detail
378    }
379}
380
381impl ErrorDetail {
382    /// Construct a new `Error` from a `SpawnError`.
383    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> ErrorDetail {
384        ErrorDetail::Spawn {
385            spawning,
386            cause: Arc::new(err),
387        }
388    }
389}
390
391impl Display for Error {
392    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
393        write!(f, "tor: {}: {}", self.detail.kind(), self.detail)
394    }
395}
396
397impl tor_error::HasKind for Error {
398    fn kind(&self) -> ErrorKind {
399        self.detail.kind()
400    }
401}
402
403impl tor_error::HasKind for ErrorDetail {
404    fn kind(&self) -> ErrorKind {
405        use ErrorDetail as E;
406        use ErrorKind as EK;
407        match self {
408            E::ObtainExitCircuit { cause, .. } => cause.kind(),
409            #[cfg(feature = "onion-service-client")]
410            E::ObtainHsCircuit { cause, .. } => cause.kind(),
411            E::ExitTimeout => EK::RemoteNetworkTimeout,
412            E::BootstrapRequired { .. } => EK::BootstrapRequired,
413            E::MemquotaSetup(e) => e.kind(),
414            E::MemquotaDuringStartup(e) => e.kind(),
415            E::GuardMgrSetup(e) => e.kind(),
416            #[cfg(all(
417                feature = "vanguards",
418                any(feature = "onion-service-client", feature = "onion-service-service")
419            ))]
420            E::VanguardMgrSetup(e) => e.kind(),
421            #[cfg(feature = "bridge-client")]
422            E::BridgeDescMgrSetup(e) => e.kind(),
423            E::CircMgrSetup(e) => e.kind(),
424            E::DirMgrSetup(e) => e.kind(),
425            E::StateMgrSetup(e) => e.kind(),
426            #[cfg(feature = "onion-service-client")]
427            E::HsClientConnectorSetup(e) => e.kind(),
428            #[cfg(feature = "onion-service-service")]
429            E::OnionServiceSetup(e) => e.kind(),
430            E::DirMgrBootstrap(e) => e.kind(),
431            #[cfg(feature = "pt-client")]
432            E::PluggableTransport(e) => e.kind(),
433            E::StreamFailed { cause, .. } => cause.kind(),
434            E::StateAccess(e) => e.kind(),
435            E::Configuration(e) => e.kind(),
436            E::Reconfigure(e) => e.kind(),
437            E::Spawn { cause, .. } => cause.kind(),
438            E::OnionAddressNotSupported => EK::FeatureDisabled,
439            E::OnionAddressResolveRequest => EK::NotImplemented,
440            #[cfg(feature = "onion-service-client")]
441            E::OnionAddressDisabled => EK::ForbiddenStreamTarget,
442            #[cfg(feature = "onion-service-client")]
443            E::BadOnionAddress(_) => EK::InvalidStreamTarget,
444            #[cfg(feature = "onion-service-service")]
445            E::LaunchOnionService(e) => e.kind(),
446            E::Address(e) => e.kind(),
447            E::InvalidHostname => EK::InvalidStreamTarget,
448            E::LocalAddress => EK::ForbiddenStreamTarget,
449            E::NoRoutableAddress => EK::RemoteHostNotFound,
450            E::ChanMgrSetup(e) => e.kind(),
451            E::NoDir { error, .. } => error.kind(),
452            E::Keystore(e) => e.kind(),
453            E::KeystoreRequired { .. } => EK::InvalidConfig,
454            E::BadClientSpecifier(_) => EK::InvalidConfig,
455            E::FsMistrust(_) => EK::FsPermissions,
456            E::MissingProtocol(_) => EK::SoftwareDeprecated,
457            E::Bug(e) => e.kind(),
458        }
459    }
460}
461
462impl From<TorAddrError> for Error {
463    fn from(e: TorAddrError) -> Error {
464        ErrorDetail::from(e).into()
465    }
466}
467
468impl From<tor_keymgr::Error> for Error {
469    fn from(e: tor_keymgr::Error) -> Error {
470        ErrorDetail::Keystore(e).into()
471    }
472}
473
474impl From<TorAddrError> for ErrorDetail {
475    fn from(e: TorAddrError) -> ErrorDetail {
476        use ErrorDetail as E;
477        use TorAddrError as TAE;
478        match e {
479            TAE::InvalidHostname => E::InvalidHostname,
480            TAE::NoPort | TAE::BadPort => E::Address(e),
481        }
482    }
483}
484
485/// Verbose information about an error, meant to provide detail or justification
486/// for user-facing errors, rather than the normal short message for
487/// developer-facing errors.
488///
489/// User-facing code may attempt to produce this by calling [`Error::hint`].
490/// Not all errors may wish to provide verbose messages. `Some(ErrorHint)` will be
491/// returned if hinting is supported for the error. Err(()) will be returned otherwise.
492/// Which errors support hinting, and the hint content, have no SemVer warranty and may
493/// change in patch versions without warning. Callers should handle both cases,
494/// falling back on the original error message in case of Err.
495///
496/// Since the internal machinery for constructing and displaying hints may change over time,
497/// no data members are currently exposed. In the future we may wish to offer an unstable
498/// API locked behind a feature, like we do with ErrorDetail.
499#[derive(Clone, Debug)]
500pub struct ErrorHint<'a> {
501    /// The pieces of the message to display to the user
502    inner: ErrorHintInner<'a>,
503}
504
505/// An inner enumeration, describing different kinds of error hint that we know how to give.
506#[derive(Clone, Debug)]
507enum ErrorHintInner<'a> {
508    /// There is a misconfigured filesystem permission, reported by `fs-mistrust`.
509    ///
510    /// Tell the user to make their file more private, or to disable `fs-mistrust`.
511    BadPermission {
512        /// The location of the file.
513        filename: &'a std::path::Path,
514        /// The access bits set on the file.
515        bits: u32,
516        /// The access bits that, according to fs-mistrust, should not be set.
517        badbits: u32,
518    },
519
520    /// At least one required protocol was missing.
521    MissingProtocols {
522        /// The list of missing required protocols
523        required: &'a tor_protover::Protocols,
524    },
525}
526
527// TODO: Perhaps we want to lower this logic to fs_mistrust crate, and have a
528// separate `ErrorHint` type for each crate that can originate a hint.  But I'd
529// rather _not_ have that turn into something that forces us to give a Hint for
530// every intermediate crate.
531impl<'a> Display for ErrorHint<'a> {
532    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533        use fs_mistrust::anon_home::PathExt as _;
534
535        match self.inner {
536            ErrorHintInner::BadPermission {
537                filename,
538                bits,
539                badbits,
540            } => {
541                writeln!(
542                    f,
543                    "Permissions are set too permissively on {}: currently {}",
544                    filename.anonymize_home(),
545                    fs_mistrust::format_access_bits(bits, '=')
546                )?;
547                if 0 != badbits & 0o222 {
548                    writeln!(
549                        f,
550                        "* Untrusted users could modify its contents and override our behavior.",
551                    )?;
552                }
553                if 0 != badbits & 0o444 {
554                    writeln!(f, "* Untrusted users could read its contents.")?;
555                }
556                writeln!(
557                    f,
558                    "You can fix this by further restricting the permissions of your filesystem, using:\n\
559                         chmod {} {}",
560                    fs_mistrust::format_access_bits(badbits, '-'),
561                    filename.anonymize_home()
562                )?;
563                writeln!(
564                    f,
565                    "You can suppress this message by setting storage.permissions.dangerously_trust_everyone=true,\n\
566                    or setting ARTI_FS_DISABLE_PERMISSION_CHECKS=yes in your environment."
567                )?;
568            }
569            ErrorHintInner::MissingProtocols { required } => {
570                writeln!(
571                    f,
572                    "The consensus directory says that we need to support certain protocols which we do not implement."
573                )?;
574                writeln!(f, "The missing protocols are: {}", required)?;
575                writeln!(
576                    f,
577                    "The best solution is to upgrade to a more recent version of Arti."
578                )?;
579            }
580        }
581        Ok(())
582    }
583}
584
585impl Error {
586    /// Return a hint object explaining how to solve this error, if we have one.
587    ///
588    /// Most errors won't have obvious hints, but some do.  For the ones that
589    /// do, we can return an [`ErrorHint`].
590    ///
591    /// Right now, `ErrorHint` is completely opaque: the only supported option
592    /// is to format it for human consumption.
593    pub fn hint(&self) -> Option<ErrorHint> {
594        HintableError::hint(self)
595    }
596}
597
598#[cfg(test)]
599mod test {
600    // @@ begin test lint list maintained by maint/add_warning @@
601    #![allow(clippy::bool_assert_comparison)]
602    #![allow(clippy::clone_on_copy)]
603    #![allow(clippy::dbg_macro)]
604    #![allow(clippy::mixed_attributes_style)]
605    #![allow(clippy::print_stderr)]
606    #![allow(clippy::print_stdout)]
607    #![allow(clippy::single_char_pattern)]
608    #![allow(clippy::unwrap_used)]
609    #![allow(clippy::unchecked_time_subtraction)]
610    #![allow(clippy::useless_vec)]
611    #![allow(clippy::needless_pass_by_value)]
612    #![allow(clippy::string_slice)] // See arti#2571
613    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
614    use super::*;
615
616    /// This code makes sure that our errors implement all the traits we want.
617    #[test]
618    fn traits_ok() {
619        // I had intended to use `assert_impl`, but that crate can't check whether
620        // a type is 'static.
621        fn assert<
622            T: Send + Sync + Clone + std::fmt::Debug + Display + std::error::Error + 'static,
623        >() {
624        }
625        fn check() {
626            assert::<Error>();
627            assert::<ErrorDetail>();
628        }
629        check(); // doesn't do anything, but avoids "unused function" warnings.
630    }
631}