tor_dirclient/err.rs
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
//! Declare dirclient-specific errors.
use std::sync::Arc;
use thiserror::Error;
use tor_error::{Bug, ErrorKind, HasKind};
use tor_linkspec::OwnedChanTarget;
use tor_rtcompat::TimeoutError;
use crate::SourceInfo;
/// An error originating from the tor-dirclient crate.
#[derive(Error, Debug, Clone)]
#[non_exhaustive]
#[allow(clippy::large_enum_variant)] // TODO(nickm) worth fixing as we do #587
pub enum Error {
/// Error while getting a circuit
#[error("Error while getting a circuit")]
CircMgr(#[from] tor_circmgr::Error),
/// An error that has occurred after we have contacted a directory cache and made a circuit to it.
#[error("Error fetching directory information")]
RequestFailed(#[from] RequestFailedError),
/// We ran into a problem that is probably due to a programming issue.
#[error("Internal error")]
Bug(#[from] Bug),
}
/// An error that has occurred after we have contacted a directory cache and made a circuit to it.
#[derive(Error, Debug, Clone)]
#[allow(clippy::exhaustive_structs)] // TODO should not be exhaustive
#[error("Request failed{}", FromSource(.source))]
pub struct RequestFailedError {
/// The source that gave us this error.
pub source: Option<SourceInfo>,
/// The underlying error that occurred.
#[source]
pub error: RequestError,
}
/// Helper type to display an optional source of directory information.
struct FromSource<'a>(&'a Option<SourceInfo>);
impl std::fmt::Display for FromSource<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(si) = self.0 {
write!(f, " from {}", si)
} else {
Ok(())
}
}
}
/// An error originating from the tor-dirclient crate.
#[derive(Error, Debug, Clone)]
#[non_exhaustive]
pub enum RequestError {
/// The directory cache took too long to reply to us.
#[error("directory timed out")]
DirTimeout,
/// We got an EOF before we were done with the headers.
#[error("truncated HTTP headers")]
TruncatedHeaders,
/// Received a response that was longer than we expected.
#[error("response too long; gave up after {0} bytes")]
ResponseTooLong(usize),
/// Data received was not UTF-8 encoded.
#[error("Couldn't decode data as UTF-8.")]
Utf8Encoding(#[from] std::string::FromUtf8Error),
/// Io error while reading on connection
#[error("IO error")]
IoError(#[source] Arc<std::io::Error>),
/// A protocol error while launching a stream
#[error("Protocol error while launching a stream")]
Proto(#[from] tor_proto::Error),
/// Error when parsing http
#[error("Couldn't parse HTTP headers")]
HttparseError(#[from] httparse::Error),
/// Error while creating http request
//
// TODO this should be abolished, in favour of a `Bug` variant,
// so that we get a stack trace, as per the notes for EK::Internal.
// We could convert via into_internal!, or a custom `From` impl.
#[error("Couldn't create HTTP request")]
HttpError(#[source] Arc<http::Error>),
/// Unrecognized content-encoding
#[error("Unrecognized content encoding: {0:?}")]
ContentEncoding(String),
/// Too much clock skew between us and the directory.
///
/// (We've giving up on this request early, since any directory that it
/// believes in, we would reject as untimely.)
#[error("Too much clock skew with directory cache")]
TooMuchClockSkew,
/// We tried to launch a request without any requested objects.
///
/// This can happen if (for example) we request an empty list of
/// microdescriptors or certificates.
#[error("We didn't have any objects to request")]
EmptyRequest,
/// HTTP status code indicates a not completely successful request
#[error("HTTP status code {0}: {1:?}")]
HttpStatus(u16, String),
}
impl From<TimeoutError> for RequestError {
fn from(_: TimeoutError) -> Self {
RequestError::DirTimeout
}
}
impl From<std::io::Error> for RequestError {
fn from(err: std::io::Error) -> Self {
Self::IoError(Arc::new(err))
}
}
impl From<http::Error> for RequestError {
fn from(err: http::Error) -> Self {
Self::HttpError(Arc::new(err))
}
}
impl Error {
/// Return true if this error means that the circuit shouldn't be used
/// for any more directory requests.
pub fn should_retire_circ(&self) -> bool {
// TODO: probably this is too aggressive, and we should
// actually _not_ dump the circuit under all circumstances.
match self {
Error::CircMgr(_) => true, // should be unreachable.
Error::RequestFailed(RequestFailedError { error, .. }) => error.should_retire_circ(),
Error::Bug(_) => true,
}
}
/// Return the peer or peers that are to be blamed for the error.
///
/// (This can return multiple peers if the request failed because multiple
/// circuit attempts all failed.)
pub fn cache_ids(&self) -> Vec<&OwnedChanTarget> {
match &self {
Error::CircMgr(e) => e.peers(),
Error::RequestFailed(RequestFailedError {
source: Some(source),
..
}) => vec![source.cache_id()],
_ => Vec::new(),
}
}
}
impl RequestError {
/// Return true if this error means that the circuit shouldn't be used
/// for any more directory requests.
pub fn should_retire_circ(&self) -> bool {
// TODO: probably this is too aggressive, and we should
// actually _not_ dump the circuit under all circumstances.
true
}
}
impl HasKind for RequestError {
fn kind(&self) -> ErrorKind {
use ErrorKind as EK;
use RequestError as E;
match self {
E::DirTimeout => EK::TorNetworkTimeout,
E::TruncatedHeaders => EK::TorProtocolViolation,
E::ResponseTooLong(_) => EK::TorProtocolViolation,
E::Utf8Encoding(_) => EK::TorProtocolViolation,
// TODO: it would be good to get more information out of the IoError
// in this case, but that would require a bunch of gnarly
// downcasting.
E::IoError(_) => EK::TorDirectoryError,
E::Proto(e) => e.kind(),
E::HttparseError(_) => EK::TorProtocolViolation,
E::HttpError(_) => EK::Internal,
E::ContentEncoding(_) => EK::TorProtocolViolation,
E::TooMuchClockSkew => EK::TorDirectoryError,
E::EmptyRequest => EK::Internal,
E::HttpStatus(_, _) => EK::TorDirectoryError,
}
}
}
impl HasKind for RequestFailedError {
fn kind(&self) -> ErrorKind {
self.error.kind()
}
}
impl HasKind for Error {
fn kind(&self) -> ErrorKind {
use Error as E;
match self {
E::CircMgr(e) => e.kind(),
E::RequestFailed(e) => e.kind(),
E::Bug(e) => e.kind(),
}
}
}