tor_hsservice/
err.rs

1//! Declare an error type for the `tor-hsservice` crate.
2
3use crate::internal_prelude::*;
4
5pub use crate::rend_handshake::{EstablishSessionError, IntroRequestError};
6
7/// An error which occurs trying to create and start up an onion service
8///
9/// This is only returned by startup methods.
10/// After the service is created and started,
11/// we will continue to try keep the service alive,
12/// retrying things as necessary.
13#[derive(Clone, Debug, Error)]
14#[non_exhaustive]
15pub enum StartupError {
16    /// A keystore operation failed.
17    #[error("Keystore error while attempting to {action}")]
18    Keystore {
19        /// The action we were trying to perform.
20        action: &'static str,
21        /// The underlying error
22        #[source]
23        cause: tor_keymgr::Error,
24    },
25
26    /// Keystore corruption.
27    #[error("The keystore is unrecoverably corrupt")]
28    KeystoreCorrupted,
29
30    /// Trouble reading on-disk state
31    #[error("reading on-disk state")]
32    // Not #[from] as that might allow call sites that were *storing* during startup
33    // to accidentally use this variant.  (Such call sites probably shouldn't exist.)
34    LoadState(#[source] tor_persist::Error),
35
36    /// Unable to access on-disk state
37    #[error("Unable to access on-disk state")]
38    StateDirectoryInaccessible(#[source] tor_persist::Error),
39
40    /// Unable to access on-disk state using underlying IO operations
41    #[error("Unable to access on-disk state: {action} {}", path.display_lossy())]
42    // TODO ideally we'd like to use StateDirectoryInaccessiblePersist and tor_persist::Error
43    // for this too, but tor_persist::Error is quite awkward.
44    StateDirectoryInaccessibleIo {
45        /// What happened
46        #[source]
47        source: Arc<io::Error>,
48
49        /// What filesystem path we were trying to access
50        path: PathBuf,
51
52        /// What we were trying to do to it
53        //
54        // TODO this should be an enum, not a static string, but see above
55        action: &'static str,
56    },
57
58    /// Fatal error (during startup)
59    #[error("fatal error")]
60    Fatal(#[from] FatalError),
61
62    /// Unable to spawn task
63    //
64    // TODO too many types have an open-coded version of FooError::Spawn
65    // Instead we should:
66    //  * Have tor_rtcompat provide a SpawnError struct which contains the task identifier
67    //  * Have tor_rtcompat provide a spawn method that takes an identifier
68    //    (and which passes that identifier to runtimes that support such a thing,
69    //    including our own mock spawner)
70    //  * Change every crate's task spawning and error handling to use the new things
71    //    (breaking changes to the error type, unless we retain unused compat error variants)
72    //
73    // TODO HSS replace this with a conversion to StartupError::Fatal(FatalError::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    /// Tried to launch an onion service that has already been launched.
84    #[error("Onion service has already been launched")]
85    AlreadyLaunched,
86}
87
88impl HasKind for StartupError {
89    fn kind(&self) -> ErrorKind {
90        use ErrorKind as EK;
91        use StartupError as E;
92        match self {
93            E::Keystore { cause, .. } => cause.kind(),
94            E::KeystoreCorrupted => EK::KeystoreCorrupted,
95            E::Spawn { cause, .. } => cause.kind(),
96            E::AlreadyLaunched => EK::BadApiUsage,
97            E::LoadState(e) => e.kind(),
98            E::StateDirectoryInaccessible(e) => e.kind(),
99            E::StateDirectoryInaccessibleIo { .. } => EK::PersistentStateAccessFailed,
100            E::Fatal(e) => e.kind(),
101        }
102    }
103}
104
105impl From<Bug> for StartupError {
106    fn from(bug: Bug) -> StartupError {
107        FatalError::from(bug).into()
108    }
109}
110
111/// An error which occurs trying to communicate with a particular client.
112///
113/// This is returned by `RendRequest::accept` and `StreamRequest::accept`.
114#[derive(Clone, Debug, Error)]
115#[non_exhaustive]
116pub enum ClientError {
117    /// Failed to process an INTRODUCE2 request.
118    #[error("Could not process INTRODUCE request")]
119    BadIntroduce(#[source] IntroRequestError),
120
121    /// Failed to complete a rendezvous request.
122    #[error("Could not connect rendezvous circuit.")]
123    EstablishSession(#[source] EstablishSessionError),
124
125    /// Failed to send a CONNECTED message and get a stream.
126    #[error("Could not accept stream from rendezvous circuit")]
127    AcceptStream(#[source] tor_proto::Error),
128
129    /// Failed to send a END message and reject a stream.
130    #[error("Could not reject stream from rendezvous circuit")]
131    RejectStream(#[source] tor_proto::Error),
132}
133
134impl HasKind for ClientError {
135    fn kind(&self) -> ErrorKind {
136        match self {
137            ClientError::BadIntroduce(e) => e.kind(),
138            ClientError::EstablishSession(e) => e.kind(),
139            ClientError::AcceptStream(e) => e.kind(),
140            ClientError::RejectStream(e) => e.kind(),
141        }
142    }
143}
144
145/// Latest time to retry a failed IPT store (eg, disk full)
146//
147// TODO (#1226): should we make this configurable? Probably not; it's not clear why a
148// user would want disk failure errors to be retried on any particular interval.
149// Instead it might make more sense to consider a unified strategy for handling
150// state errors.
151const IPT_STORE_RETRY_MAX: Duration = Duration::from_secs(60);
152
153/// An error arising when trying to store introduction points
154///
155/// These don't escape the crate, except to be logged.
156///
157/// These errors might be fatal, or they might be something we should retry.
158#[derive(Clone, Debug, Error)]
159pub(crate) enum IptStoreError {
160    /// Unable to store introduction points
161    #[error("Unable to store introduction points")]
162    Store(#[from] tor_persist::Error),
163
164    /// Fatal error
165    #[error("Fatal error")]
166    Fatal(#[from] FatalError),
167}
168
169impl From<Bug> for IptStoreError {
170    fn from(bug: Bug) -> IptStoreError {
171        FatalError::from(bug).into()
172    }
173}
174
175impl IptStoreError {
176    /// Log this error, and report latest time to retry
177    ///
178    /// It's OK to retry this earlier, if we are prompted somehow by other work;
179    /// this is the longest time we should wait, so that we poll periodically
180    /// to see if the situation has improved.
181    ///
182    /// If the operation shouldn't be retried, the problem was a fatal error,
183    /// which is simply returned.
184    // TODO: should this be a HasRetryTime impl instead?  But that has different semantics.
185    pub(crate) fn log_retry_max(self, nick: &HsNickname) -> Result<Duration, FatalError> {
186        use IptStoreError as ISE;
187        let wait = match self {
188            ISE::Store(_) => IPT_STORE_RETRY_MAX,
189            ISE::Fatal(e) => return Err(e),
190        };
191        error_report!(self, "HS service {}: error", nick);
192        Ok(wait)
193    }
194}
195
196/// An error which means we cannot continue to try to operate an onion service.
197///
198/// These errors only occur during operation, and only for catastrophic reasons
199/// (such as the async reactor shutting down).
200//
201// TODO where is FatalError emitted from this crate into the wider program ?
202// Perhaps there will be some kind of monitoring handle (TODO (#1083)) that can produce one of these.
203#[derive(Clone, Debug, Error)]
204#[non_exhaustive]
205pub enum FatalError {
206    /// Unable to spawn task
207    #[error("Unable to spawn {spawning}")]
208    Spawn {
209        /// What we were trying to spawn
210        spawning: &'static str,
211        /// What happened when we tried to spawn it.
212        #[source]
213        cause: Arc<SpawnError>,
214    },
215
216    /// Failed to access the keystore.
217    #[error("failed to access keystore")]
218    Keystore(#[from] tor_keymgr::Error),
219
220    /// Failed to access the keystore due to incompatible concurrent access.
221    ///
222    /// This can only happen if someone is modifying the contents of the keystore
223    /// just as we are trying to access it.
224    #[error("keystore {action} failed for {path} (someone else is writing to the keystore?!)")]
225    KeystoreRace {
226        /// What action we were trying to perform
227        action: &'static str,
228        /// The ArtiPath we were trying to access
229        path: tor_keymgr::ArtiPath,
230    },
231
232    /// The identity keypair of the service could not be found in the keystore.
233    #[error("Hidden service identity key not found: {0}")]
234    MissingHsIdKeypair(HsNickname),
235
236    /// IPT keys found for being-created IPT
237    ///
238    /// This could only happen if someone is messing with our RNG
239    /// or our code is completely wrong, or something.
240    #[error("IPT keys found for being-created IPT {0} (serious key management problems!)")]
241    IptKeysFoundUnexpectedly(tor_keymgr::ArtiPath),
242
243    /// The network directory provider is shutting down without giving us the
244    /// netdir we asked for.
245    #[error("{0}")]
246    NetdirProviderShutdown(#[from] tor_netdir::NetdirProviderShutdown),
247
248    /// A field was missing when we tried to construct a
249    /// [`OnionService`](crate::OnionService).
250    #[error("Missing field when constructing OnionService")]
251    MissingField(#[from] derive_builder::UninitializedFieldError),
252
253    /// Invalid restricted discovery configuration.
254    #[error("Restricted discovery is enabled, but no authorized clients are configured. Service will be unreachable")]
255    #[cfg(feature = "restricted-discovery")]
256    RestrictedDiscoveryNoClients,
257
258    /// An error caused by a programming issue . or a failure in another
259    /// library that we can't work around.
260    #[error("Programming error")]
261    Bug(#[from] Bug),
262}
263
264impl FatalError {
265    /// Construct a new `FatalError` from a `SpawnError`.
266    //
267    // TODO lots of our Errors have a function exactly like this.
268    pub(super) fn from_spawn(spawning: &'static str, err: SpawnError) -> FatalError {
269        FatalError::Spawn {
270            spawning,
271            cause: Arc::new(err),
272        }
273    }
274}
275
276impl HasKind for FatalError {
277    fn kind(&self) -> ErrorKind {
278        use ErrorKind as EK;
279        use FatalError as FE;
280        match self {
281            FE::Spawn { cause, .. } => cause.kind(),
282            FE::Keystore(e) => e.kind(),
283            FE::MissingHsIdKeypair(_) => EK::Internal, // TODO (#1256) This is not always right.
284            FE::KeystoreRace { .. } => EK::KeystoreAccessFailed,
285            FE::IptKeysFoundUnexpectedly(_) => EK::Internal, // This is indeed quite bad.
286            FE::NetdirProviderShutdown(e) => e.kind(),
287            FE::MissingField(_) => EK::BadApiUsage,
288            #[cfg(feature = "restricted-discovery")]
289            FE::RestrictedDiscoveryNoClients => EK::InvalidConfig,
290            FE::Bug(e) => e.kind(),
291        }
292    }
293}
294
295/// Error occurring in [`IptManager::expire_old_ipts_external_persistent_state`](crate::ipt_mgr::IptManager::expire_old_ipts_external_persistent_state)
296///
297/// All that happens with these errors is that they are logged
298/// (with a rate limit).
299#[derive(Error, Clone, Debug)]
300pub(crate) enum StateExpiryError {
301    /// Key expiry failed
302    #[error("key(s)")]
303    Key(#[from] tor_keymgr::Error),
304    /// Replay log expiry (or other things using `tor_persist`) failed
305    #[error("replay log(s): failed to {operation} {}", path.display_lossy())]
306    ReplayLog {
307        /// The actual error
308        #[source]
309        source: Arc<io::Error>,
310        /// The pathname
311        path: PathBuf,
312        /// What we were doing
313        operation: &'static str,
314    },
315    /// Internal error
316    #[error("internal error")]
317    Bug(#[from] Bug),
318}
319
320impl HasKind for StateExpiryError {
321    fn kind(&self) -> ErrorKind {
322        use tor_error::ErrorKind as EK;
323        use StateExpiryError as SEE;
324        match self {
325            SEE::Key(e) => e.kind(),
326            SEE::ReplayLog { .. } => EK::PersistentStateAccessFailed,
327            SEE::Bug(e) => e.kind(),
328        }
329    }
330}