tor-proto 0.41.0

Asynchronous client-side implementation of the central Tor network protocols
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
//! Define an error type for the tor-proto crate.
use safelog::Sensitive;
use std::{sync::Arc, time::Duration};
use thiserror::Error;
use tor_cell::relaycell::{StreamId, msg::EndReason};
use tor_error::{Bug, ErrorKind, HasKind};
use tor_linkspec::RelayIdType;

use crate::HopNum;
use crate::client::circuit::PathEntry;

/// An error type for the tor-proto crate.
///
/// This type should probably be split into several.  There's more
/// than one kind of error that can occur while doing something with
/// the Tor protocol.
#[derive(Error, Debug, Clone)]
#[non_exhaustive]
pub enum Error {
    /// An error that occurred in the tor_bytes crate while decoding an
    /// object.
    #[error("Unable to parse {object}")]
    BytesErr {
        /// What we were trying to parse.
        object: &'static str,
        /// The error that occurred while parsing it.
        #[source]
        err: tor_bytes::Error,
    },
    /// An error that occurred from the io system when using a
    /// channel.
    #[error("IO error on channel with peer")]
    ChanIoErr(#[source] Arc<std::io::Error>),
    /// An error from the io system that occurred when trying to connect a channel.
    #[error("IO error while handshaking with peer")]
    HandshakeIoErr(#[source] Arc<std::io::Error>),
    /// An error occurred while trying to create or encode a cell.
    #[error("Unable to generate or encode {object}")]
    CellEncodeErr {
        /// The object we were trying to create or encode.
        object: &'static str,
        /// The error that occurred.
        #[source]
        err: tor_cell::Error,
    },
    /// An error occurred while trying to decode or parse a cell.
    #[error("Error while parsing {object}")]
    CellDecodeErr {
        /// The object we were trying to decode.
        object: &'static str,
        /// The error that occurred.
        #[source]
        err: tor_cell::Error,
    },
    /// An error occurred while trying to create or encode some non-cell
    /// message.
    ///
    /// This is likely the result of a bug: either in this crate, or the code
    /// that provided the input.
    #[error("Problem while encoding {object}")]
    EncodeErr {
        /// What we were trying to create or encode.
        object: &'static str,
        /// The error that occurred.
        #[source]
        err: tor_bytes::EncodeError,
    },
    /// An error occurred while trying to decode a link specifier.
    #[error("Error while parsing {object}")]
    #[cfg(feature = "relay")]
    LinkspecDecodeErr {
        /// The object we were trying to decode.
        object: &'static str,
        /// The error that occurred.
        #[source]
        err: tor_linkspec::decode::ChanTargetDecodeError,
    },
    /// We found a problem with one of the certificates in the channel
    /// handshake.
    #[error("Problem with certificate on handshake")]
    HandshakeCertErr(#[source] tor_cert::CertError),
    /// We tried to produce too much output for a key derivation function.
    #[error("Tried to extract too many bytes from a KDF")]
    InvalidKDFOutputLength,
    /// We tried to encrypt a message to a hop that wasn't there.
    #[error("Tried to encrypt a cell for a nonexistent hop")]
    NoSuchHop,
    /// The authentication information on this cell was completely wrong,
    /// or the cell was corrupted.
    #[error("Bad relay cell authentication")]
    BadCellAuth,
    /// A circuit-extension handshake failed due to a mismatched authentication
    /// value.
    #[error("Circuit-extension handshake authentication failed")]
    BadCircHandshakeAuth,
    /// Handshake protocol violation.
    #[error("Handshake protocol violation: {0}")]
    HandshakeProto(String),
    /// Handshake broken, maybe due to clock skew.
    ///
    /// (If the problem can't be due to clock skew, we return HandshakeProto
    /// instead.)
    #[error("Handshake failed due to expired certificates (possible clock skew)")]
    HandshakeCertsExpired {
        /// For how long has the circuit been expired?
        expired_by: Duration,
    },
    /// Protocol violation at the channel level, other than at the handshake
    /// stage.
    #[error("Channel protocol violation: {0}")]
    ChanProto(String),
    /// Protocol violation at the circuit level
    #[error("Circuit protocol violation: {0}")]
    CircProto(String),
    /// Channel is closed, or became closed while we were trying to do some
    /// operation.
    #[error("Channel closed")]
    ChannelClosed(#[from] ChannelClosed),
    /// Circuit is closed, or became closed while we were trying to so some
    /// operation.
    #[error("Circuit closed")]
    CircuitClosed,
    /// Can't allocate any more circuit or stream IDs on a channel.
    #[error("Too many entries in map: can't allocate ID")]
    IdRangeFull,
    /// Received a stream request with a stream ID that is already in use for another stream.
    #[error("Stream ID {0} is already in use")]
    IdUnavailable(StreamId),
    /// Received a cell with a stream ID of zero.
    #[error("Received a cell with a stream ID of zero")]
    StreamIdZero,
    /// Received a cell on a closed or non-existent stream.
    #[error(
        "Received a cell from {} on a closed or non-existent stream {}. \
         Either they are violating the protocol, or we are expiring streams too aggressively",
        src,
        streamid
    )]
    UnknownStream {
        /// The hop the cell originated from.
        src: Sensitive<PathEntry>,
        /// The stream ID of the cell.
        streamid: StreamId,
    },
    /// Couldn't extend a circuit because the extending relay or the
    /// target relay refused our request.
    #[error("Circuit extension refused: {0}")]
    CircRefused(&'static str),
    /// Tried to make or use a stream to an invalid destination address.
    #[error("Invalid stream target address")]
    BadStreamAddress,
    /// Received an End cell from the other end of a stream.
    #[error("Received an END cell with reason {0}")]
    EndReceived(EndReason),
    /// Stream was already closed when we tried to use it.
    #[error("Stream not connected")]
    NotConnected,
    /// Stream protocol violation
    #[error("Stream protocol violation: {0}")]
    StreamProto(String),

    /// Excessive data received from a circuit hop.
    #[error("Received too many inbound cells")]
    ExcessInboundCells,
    /// Tried to send too many cells to a circuit hop.
    #[error("Tried to send too many outbound cells")]
    ExcessOutboundCells,

    /// Received unexpected or excessive inbound circuit
    #[error("Received unexpected or excessive circuit padding from {}", _1.display())]
    ExcessPadding(#[source] ExcessPadding, HopNum),

    /// Channel does not match target
    #[error("Peer identity mismatch: {0}")]
    ChanMismatch(String),
    /// There was a programming error somewhere in our code, or the calling code.
    #[error("Programming error")]
    Bug(#[from] tor_error::Bug),
    /// Remote DNS lookup failed.
    #[error("Remote resolve failed")]
    ResolveError(#[source] ResolveError),
    /// We tried to do something with a that we couldn't, because of an identity key type
    /// that the relay doesn't have.
    #[error("Relay has no {0} identity")]
    MissingId(RelayIdType),
    /// Memory quota error
    #[error("memory quota error")]
    Memquota(#[from] tor_memquota::Error),
    /// An unsupported authentication type value. This is raised during the relay <-> relay channel
    /// handshake if the authentication presented from the other side is unsupported.
    #[cfg(feature = "relay")]
    #[error("Authentication type {0} is unsupported")]
    UnsupportedAuth(u16),
}

/// Error which indicates that the channel was closed.
#[derive(Error, Debug, Clone)]
#[error("Channel closed")]
pub struct ChannelClosed;

impl HasKind for ChannelClosed {
    fn kind(&self) -> ErrorKind {
        ErrorKind::CircuitCollapse
    }
}

/// Details about an error received while resolving a domain
#[derive(Error, Debug, Clone)]
#[non_exhaustive]
pub enum ResolveError {
    /// A transient error which can be retried
    #[error("Received retriable transient error")]
    Transient,
    /// A non transient error, which shouldn't be retried
    #[error("Received non-retriable error")]
    Nontransient,
    /// Could not parse the response properly
    #[error("Received unrecognized result")]
    Unrecognized,
}

impl Error {
    /// Create an error from a tor_cell error that has occurred while trying to
    /// encode or create something of type `object`
    pub(crate) fn from_cell_enc(err: tor_cell::Error, object: &'static str) -> Error {
        Error::CellEncodeErr { object, err }
    }

    /// Create an error for a tor_bytes error that occurred while parsing
    /// something of type `object`.
    pub(crate) fn from_bytes_err(err: tor_bytes::Error, object: &'static str) -> Error {
        Error::BytesErr { err, object }
    }

    /// Create an error for a tor_bytes error that occurred while encoding
    /// something of type `object`.
    pub(crate) fn from_bytes_enc(err: tor_bytes::EncodeError, object: &'static str) -> Error {
        Error::EncodeErr { err, object }
    }
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Self {
        Self::ChanIoErr(Arc::new(err))
    }
}

impl From<Error> for std::io::Error {
    fn from(err: Error) -> std::io::Error {
        use Error::*;
        use std::io::ErrorKind;
        let kind = match err {
            ChanIoErr(e) | HandshakeIoErr(e) => match Arc::try_unwrap(e) {
                Ok(e) => return e,
                Err(arc) => return std::io::Error::new(arc.kind(), arc),
            },

            InvalidKDFOutputLength | NoSuchHop | BadStreamAddress => ErrorKind::InvalidInput,

            NotConnected => ErrorKind::NotConnected,

            EndReceived(end_reason) => end_reason.into(),

            CircuitClosed => ErrorKind::ConnectionReset,

            Memquota { .. } => ErrorKind::OutOfMemory,

            BytesErr { .. }
            | BadCellAuth
            | BadCircHandshakeAuth
            | HandshakeProto(_)
            | HandshakeCertErr(_)
            | ChanProto(_)
            | HandshakeCertsExpired { .. }
            | ChannelClosed(_)
            | CircProto(_)
            | CellDecodeErr { .. }
            | CellEncodeErr { .. }
            | EncodeErr { .. }
            | ChanMismatch(_)
            | StreamProto(_)
            | MissingId(_)
            | IdUnavailable(_)
            | StreamIdZero
            | UnknownStream { .. }
            | ExcessInboundCells
            | ExcessOutboundCells
            | ExcessPadding(_, _) => ErrorKind::InvalidData,

            #[cfg(feature = "relay")]
            LinkspecDecodeErr { .. } => ErrorKind::InvalidData,
            #[cfg(feature = "relay")]
            UnsupportedAuth(_) => ErrorKind::Unsupported,

            Bug(ref e) if e.kind() == tor_error::ErrorKind::BadApiUsage => ErrorKind::InvalidData,

            IdRangeFull | CircRefused(_) | ResolveError(_) | Bug(_) => ErrorKind::Other,
        };
        std::io::Error::new(kind, err)
    }
}

impl HasKind for Error {
    fn kind(&self) -> ErrorKind {
        use Error as E;
        use ErrorKind as EK;
        use tor_bytes::Error as BytesError;
        match self {
            E::BytesErr {
                err: BytesError::Bug(e),
                ..
            } => e.kind(),
            E::BytesErr { .. } => EK::TorProtocolViolation,
            E::ChanIoErr(_) => EK::LocalNetworkError,
            E::HandshakeIoErr(_) => EK::TorAccessFailed,
            E::HandshakeCertErr(_) => EK::TorProtocolViolation,
            E::CellEncodeErr { err, .. } => err.kind(),
            E::CellDecodeErr { err, .. } => err.kind(),
            #[cfg(feature = "relay")]
            E::LinkspecDecodeErr { .. } => EK::TorProtocolViolation,
            E::EncodeErr { .. } => EK::BadApiUsage,
            E::InvalidKDFOutputLength => EK::Internal,
            E::NoSuchHop => EK::BadApiUsage,
            E::BadCellAuth => EK::TorProtocolViolation,
            E::BadCircHandshakeAuth => EK::TorProtocolViolation,
            E::HandshakeProto(_) => EK::TorAccessFailed,
            E::HandshakeCertsExpired { .. } => EK::ClockSkew,
            E::ChanProto(_) => EK::TorProtocolViolation,
            E::CircProto(_) => EK::TorProtocolViolation,
            E::ChannelClosed(e) => e.kind(),
            E::CircuitClosed => EK::CircuitCollapse,
            E::IdRangeFull => EK::BadApiUsage,
            E::CircRefused(_) => EK::CircuitRefused,
            E::BadStreamAddress => EK::BadApiUsage,
            E::EndReceived(reason) => reason.kind(),
            E::NotConnected => EK::BadApiUsage,
            E::StreamProto(_) => EK::TorProtocolViolation,
            E::ChanMismatch(_) => EK::RelayIdMismatch,
            E::ResolveError(ResolveError::Nontransient) => EK::RemoteHostNotFound,
            E::ResolveError(ResolveError::Transient) => EK::RemoteHostResolutionFailed,
            E::ResolveError(ResolveError::Unrecognized) => EK::RemoteHostResolutionFailed,
            E::MissingId(_) => EK::BadApiUsage,
            E::IdUnavailable(_) => EK::BadApiUsage,
            E::StreamIdZero => EK::BadApiUsage,
            E::UnknownStream { .. } => EK::TorProtocolViolation,
            E::ExcessInboundCells => EK::TorProtocolViolation,
            E::ExcessOutboundCells => EK::Internal,
            E::ExcessPadding(_, _) => EK::TorProtocolViolation,
            E::Memquota(err) => err.kind(),
            E::Bug(e) => e.kind(),
            #[cfg(feature = "relay")]
            E::UnsupportedAuth(_) => EK::RemoteProtocolViolation,
        }
    }
}

/// Internal type: Error return value from reactor's run_once
/// function: indicates an error or a shutdown.
#[derive(Debug)]
pub(crate) enum ReactorError {
    /// The reactor should shut down with an abnormal exit condition.
    Err(Error),
    /// The reactor should shut down without an error, since all is well.
    Shutdown,
}

impl From<Error> for ReactorError {
    fn from(e: Error) -> ReactorError {
        ReactorError::Err(e)
    }
}

impl From<ChannelClosed> for ReactorError {
    fn from(e: ChannelClosed) -> ReactorError {
        ReactorError::Err(e.into())
    }
}

impl From<Bug> for ReactorError {
    fn from(e: Bug) -> ReactorError {
        ReactorError::Err(e.into())
    }
}

#[cfg(test)]
impl ReactorError {
    /// Tests only: assert that this is an Error, and return it.
    pub(crate) fn unwrap_err(self) -> Error {
        match self {
            ReactorError::Shutdown => panic!(),
            ReactorError::Err(e) => e,
        }
    }
}

/// An error type for client-side conflux handshakes.
#[derive(Debug)]
#[cfg(feature = "conflux")]
#[allow(unused)] // TODO(conflux): remove
pub(crate) enum ConfluxHandshakeError {
    /// Timeout while waiting for CONFLUX_LINKED response.
    Timeout,
    /// An error that occurred while sending the CONFLUX_LINK message.
    Link(Error),
    /// The channel was closed.
    ChannelClosed,
}

/// An error returned when we receive excessive or unexpected padding.
#[derive(Debug, Clone, Error)]
#[non_exhaustive]
pub enum ExcessPadding {
    /// We received circuit padding when we hadn't negotiated a padding framework.
    #[error("Padding received when not negotiated with given hop")]
    NoPaddingNegotiated,
    /// We received more padding than our negotiated framework permits.
    #[error("Received padding in excess of negotiated framework's limit")]
    PaddingExceedsLimit,
}