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}