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::Redacted;
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}")]
193 ObtainHsCircuit {
194 /// The service we were trying to connect to
195 hsid: Redacted<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_proto::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 /// Building configuration for the client failed.
263 #[error("Problem with configuration")]
264 Configuration(#[from] tor_config::ConfigBuildError),
265
266 /// Unable to change configuration.
267 #[error("Unable to change configuration")]
268 Reconfigure(#[from] tor_config::ReconfigureError),
269
270 /// Problem creating or launching a pluggable transport.
271 #[cfg(feature="pt-client")]
272 #[error("Problem with a pluggable transport")]
273 PluggableTransport(#[from] tor_ptmgr::err::PtError),
274
275 /// We encountered a problem while inspecting or creating a directory.
276 #[error("Problem accessing filesystem")]
277 FsMistrust(#[from] fs_mistrust::Error),
278
279 /// Unable to spawn task
280 #[error("Unable to spawn {spawning}")]
281 Spawn {
282 /// What we were trying to spawn.
283 spawning: &'static str,
284 /// What happened when we tried to spawn it.
285 #[source]
286 cause: Arc<SpawnError>
287 },
288
289 /// Attempted to use an unbootstrapped `TorClient` for something that
290 /// requires bootstrapping to have completed.
291 #[error("Cannot {action} with unbootstrapped client")]
292 BootstrapRequired {
293 /// What we were trying to do that required bootstrapping.
294 action: &'static str
295 },
296
297 /// Attempted to use a `TorClient` for something when it did not
298 /// have a valid directory.
299 #[error("Tried to {action} without a valid directory")]
300 NoDir {
301 /// The underlying error.
302 #[source]
303 error: tor_netdir::Error,
304 /// What we were trying to do that needed a directory.
305 action: &'static str,
306 },
307
308 /// A key store access failed.
309 #[error("Error while trying to access a key store")]
310 Keystore(#[from] tor_keymgr::Error),
311
312 /// Attempted to use a `TorClient` for something that
313 /// requires the keystore to be enabled in the configuration.
314 #[error("Cannot {action} without enabling storage.keystore")]
315 KeystoreRequired {
316 /// What we were trying to do that required the keystore to be enabled.
317 action: &'static str
318 },
319
320 /// Encountered a malformed client specifier.
321 #[error("Bad client specifier")]
322 BadClientSpecifier(#[from] tor_keymgr::ArtiPathSyntaxError),
323
324 /// We tried to parse an onion address, but we found that it was invalid.
325 ///
326 /// This error occurs if we are asked to connect to an invalid .onion address.
327 #[cfg(feature = "onion-service-client")]
328 #[error("Invalid onion address")]
329 BadOnionAddress(#[from] tor_hscrypto::pk::HsIdParseError),
330
331 /// We were unable to launch an onion service, even though we
332 /// we are configured to be able to do so.
333 #[cfg(feature= "onion-service-service")]
334 #[error("Unable to launch onion service")]
335 LaunchOnionService(#[source] tor_hsservice::StartupError),
336
337 /// We found that at least one required protocol was missing.
338 #[error("Arti is missing a required protocol feature")]
339 MissingProtocol(#[source] tor_netdoc::doc::netstatus::ProtocolSupportError),
340
341 /// A programming problem, either in our code or the code calling it.
342 #[error("Programming problem")]
343 Bug(#[from] tor_error::Bug),
344}
345
346// End of the use of $vis to refer to visibility according to `error_detail`
347}
348
349#[cfg(feature = "error_detail")]
350impl Error {
351 /// Return the underlying error detail object for this error.
352 ///
353 /// In general, it's not a good idea to use this function. Our
354 /// `arti_client::ErrorDetail` objects are unstable, and matching on them is
355 /// probably not the best way to achieve whatever you're trying to do.
356 /// Instead, we recommend using the [`kind`](`tor_error::HasKind::kind`)
357 /// trait method if your program needs to distinguish among different types
358 /// of errors.
359 ///
360 /// (If the above function don't meet your needs, please let us know!)
361 ///
362 /// This function is only available when `arti-client` is built with the
363 /// `error_detail` feature. Using this function will void your semver
364 /// guarantees.
365 pub fn detail(&self) -> &ErrorDetail {
366 &self.detail
367 }
368}
369
370impl Error {
371 /// Consume this error and return the underlying error detail object.
372 pub(crate) fn into_detail(self) -> ErrorDetail {
373 *self.detail
374 }
375}
376
377impl ErrorDetail {
378 /// Construct a new `Error` from a `SpawnError`.
379 pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> ErrorDetail {
380 ErrorDetail::Spawn {
381 spawning,
382 cause: Arc::new(err),
383 }
384 }
385}
386
387impl Display for Error {
388 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
389 write!(f, "tor: {}: {}", self.detail.kind(), &self.detail)
390 }
391}
392
393impl tor_error::HasKind for Error {
394 fn kind(&self) -> ErrorKind {
395 self.detail.kind()
396 }
397}
398
399impl tor_error::HasKind for ErrorDetail {
400 fn kind(&self) -> ErrorKind {
401 use ErrorDetail as E;
402 use ErrorKind as EK;
403 match self {
404 E::ObtainExitCircuit { cause, .. } => cause.kind(),
405 #[cfg(feature = "onion-service-client")]
406 E::ObtainHsCircuit { cause, .. } => cause.kind(),
407 E::ExitTimeout => EK::RemoteNetworkTimeout,
408 E::BootstrapRequired { .. } => EK::BootstrapRequired,
409 E::MemquotaSetup(e) => e.kind(),
410 E::MemquotaDuringStartup(e) => e.kind(),
411 E::GuardMgrSetup(e) => e.kind(),
412 #[cfg(all(
413 feature = "vanguards",
414 any(feature = "onion-service-client", feature = "onion-service-service")
415 ))]
416 E::VanguardMgrSetup(e) => e.kind(),
417 #[cfg(feature = "bridge-client")]
418 E::BridgeDescMgrSetup(e) => e.kind(),
419 E::CircMgrSetup(e) => e.kind(),
420 E::DirMgrSetup(e) => e.kind(),
421 E::StateMgrSetup(e) => e.kind(),
422 #[cfg(feature = "onion-service-client")]
423 E::HsClientConnectorSetup(e) => e.kind(),
424 #[cfg(feature = "onion-service-service")]
425 E::OnionServiceSetup(e) => e.kind(),
426 E::DirMgrBootstrap(e) => e.kind(),
427 #[cfg(feature = "pt-client")]
428 E::PluggableTransport(e) => e.kind(),
429 E::StreamFailed { cause, .. } => cause.kind(),
430 E::StateAccess(e) => e.kind(),
431 E::Configuration(e) => e.kind(),
432 E::Reconfigure(e) => e.kind(),
433 E::Spawn { cause, .. } => cause.kind(),
434 E::OnionAddressNotSupported => EK::FeatureDisabled,
435 E::OnionAddressResolveRequest => EK::NotImplemented,
436 #[cfg(feature = "onion-service-client")]
437 E::OnionAddressDisabled => EK::ForbiddenStreamTarget,
438 #[cfg(feature = "onion-service-client")]
439 E::BadOnionAddress(_) => EK::InvalidStreamTarget,
440 #[cfg(feature = "onion-service-service")]
441 E::LaunchOnionService(e) => e.kind(),
442 E::Address(e) => e.kind(),
443 E::InvalidHostname => EK::InvalidStreamTarget,
444 E::LocalAddress => EK::ForbiddenStreamTarget,
445 E::ChanMgrSetup(e) => e.kind(),
446 E::NoDir { error, .. } => error.kind(),
447 E::Keystore(e) => e.kind(),
448 E::KeystoreRequired { .. } => EK::InvalidConfig,
449 E::BadClientSpecifier(_) => EK::InvalidConfig,
450 E::FsMistrust(_) => EK::FsPermissions,
451 E::MissingProtocol(_) => EK::SoftwareDeprecated,
452 E::Bug(e) => e.kind(),
453 }
454 }
455}
456
457impl From<TorAddrError> for Error {
458 fn from(e: TorAddrError) -> Error {
459 ErrorDetail::from(e).into()
460 }
461}
462
463impl From<tor_keymgr::Error> for Error {
464 fn from(e: tor_keymgr::Error) -> Error {
465 ErrorDetail::Keystore(e).into()
466 }
467}
468
469impl From<TorAddrError> for ErrorDetail {
470 fn from(e: TorAddrError) -> ErrorDetail {
471 use ErrorDetail as E;
472 use TorAddrError as TAE;
473 match e {
474 TAE::InvalidHostname => E::InvalidHostname,
475 TAE::NoPort | TAE::BadPort => E::Address(e),
476 }
477 }
478}
479
480/// Verbose information about an error, meant to provide detail or justification
481/// for user-facing errors, rather than the normal short message for
482/// developer-facing errors.
483///
484/// User-facing code may attempt to produce this by calling [`Error::hint`].
485/// Not all errors may wish to provide verbose messages. `Some(ErrorHint)` will be
486/// returned if hinting is supported for the error. Err(()) will be returned otherwise.
487/// Which errors support hinting, and the hint content, have no SemVer warranty and may
488/// change in patch versions without warning. Callers should handle both cases,
489/// falling back on the original error message in case of Err.
490///
491/// Since the internal machinery for constructing and displaying hints may change over time,
492/// no data members are currently exposed. In the future we may wish to offer an unstable
493/// API locked behind a feature, like we do with ErrorDetail.
494#[derive(Clone, Debug)]
495pub struct ErrorHint<'a> {
496 /// The pieces of the message to display to the user
497 inner: ErrorHintInner<'a>,
498}
499
500/// An inner enumeration, describing different kinds of error hint that we know how to give.
501#[derive(Clone, Debug)]
502enum ErrorHintInner<'a> {
503 /// There is a misconfigured filesystem permission, reported by `fs-mistrust`.
504 ///
505 /// Tell the user to make their file more private, or to disable `fs-mistrust`.
506 BadPermission {
507 /// The location of the file.
508 filename: &'a std::path::Path,
509 /// The access bits set on the file.
510 bits: u32,
511 /// The access bits that, according to fs-mistrust, should not be set.
512 badbits: u32,
513 },
514
515 /// At least one required protocol was missing.
516 MissingProtocols {
517 /// The list of missing required protocols
518 required: &'a tor_protover::Protocols,
519 },
520}
521
522// TODO: Perhaps we want to lower this logic to fs_mistrust crate, and have a
523// separate `ErrorHint` type for each crate that can originate a hint. But I'd
524// rather _not_ have that turn into something that forces us to give a Hint for
525// every intermediate crate.
526impl<'a> Display for ErrorHint<'a> {
527 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528 use fs_mistrust::anon_home::PathExt as _;
529
530 match self.inner {
531 ErrorHintInner::BadPermission {
532 filename,
533 bits,
534 badbits,
535 } => {
536 writeln!(
537 f,
538 "Permissions are set too permissively on {}: currently {}",
539 filename.anonymize_home(),
540 fs_mistrust::format_access_bits(bits, '=')
541 )?;
542 if 0 != badbits & 0o222 {
543 writeln!(
544 f,
545 "* Untrusted users could modify its contents and override our behavior.",
546 )?;
547 }
548 if 0 != badbits & 0o444 {
549 writeln!(f, "* Untrusted users could read its contents.")?;
550 }
551 writeln!(f,
552 "You can fix this by further restricting the permissions of your filesystem, using:\n\
553 chmod {} {}",
554 fs_mistrust::format_access_bits(badbits, '-'),
555 filename.anonymize_home())?;
556 writeln!(f, "You can suppress this message by setting storage.permissions.dangerously_trust_everyone=true,\n\
557 or setting ARTI_FS_DISABLE_PERMISSION_CHECKS=yes in your environment.")?;
558 }
559 ErrorHintInner::MissingProtocols { required } => {
560 writeln!(f, "The consensus directory says that we need to support certain protocols which we do not implement.")?;
561 writeln!(f, "The missing protocols are: {}", required)?;
562 writeln!(
563 f,
564"The best solution is to upgrade to a more recent version of Arti. If this is not possible,
565you can list the missing protocols in the configuration option 'use_obsolete_software.ignore_missing_required_protocols'"
566 )?;
567 }
568 }
569 Ok(())
570 }
571}
572
573impl Error {
574 /// Return a hint object explaining how to solve this error, if we have one.
575 ///
576 /// Most errors won't have obvious hints, but some do. For the ones that
577 /// do, we can return an [`ErrorHint`].
578 ///
579 /// Right now, `ErrorHint` is completely opaque: the only supported option
580 /// is to format it for human consumption.
581 pub fn hint(&self) -> Option<ErrorHint> {
582 HintableError::hint(self)
583 }
584}
585
586#[cfg(test)]
587mod test {
588 // @@ begin test lint list maintained by maint/add_warning @@
589 #![allow(clippy::bool_assert_comparison)]
590 #![allow(clippy::clone_on_copy)]
591 #![allow(clippy::dbg_macro)]
592 #![allow(clippy::mixed_attributes_style)]
593 #![allow(clippy::print_stderr)]
594 #![allow(clippy::print_stdout)]
595 #![allow(clippy::single_char_pattern)]
596 #![allow(clippy::unwrap_used)]
597 #![allow(clippy::unchecked_duration_subtraction)]
598 #![allow(clippy::useless_vec)]
599 #![allow(clippy::needless_pass_by_value)]
600 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
601 use super::*;
602
603 /// This code makes sure that our errors implement all the traits we want.
604 #[test]
605 fn traits_ok() {
606 // I had intended to use `assert_impl`, but that crate can't check whether
607 // a type is 'static.
608 fn assert<
609 T: Send + Sync + Clone + std::fmt::Debug + Display + std::error::Error + 'static,
610 >() {
611 }
612 fn check() {
613 assert::<Error>();
614 assert::<ErrorDetail>();
615 }
616 check(); // doesn't do anything, but avoids "unused function" warnings.
617 }
618}