dirmgr: Remember where netdocs came from.
This isn't complete (see TODO), but it's enough to let us report the right ErrorKind if something fails to parse.
This commit is contained in:
parent
4bb7c97399
commit
2a7087ff93
|
@ -3044,6 +3044,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
|
"derive_more",
|
||||||
"digest 0.10.1",
|
"digest 0.10.1",
|
||||||
"event-listener",
|
"event-listener",
|
||||||
"float_eq",
|
"float_eq",
|
||||||
|
|
|
@ -33,6 +33,7 @@ tor-rtcompat = { path = "../tor-rtcompat", version = "0.0.4"}
|
||||||
async-trait = "0.1.2"
|
async-trait = "0.1.2"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
derive_builder = "0.10"
|
derive_builder = "0.10"
|
||||||
|
derive_more = "0.99"
|
||||||
digest = "0.10.0"
|
digest = "0.10.0"
|
||||||
event-listener = "2"
|
event-listener = "2"
|
||||||
futures = "0.3.14"
|
futures = "0.3.14"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::DocSource;
|
||||||
use futures::task::SpawnError;
|
use futures::task::SpawnError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tor_error::{ErrorKind, HasKind};
|
use tor_error::{ErrorKind, HasKind};
|
||||||
|
@ -59,8 +60,14 @@ pub enum Error {
|
||||||
#[error("Invalid hexadecimal id in directory cache")]
|
#[error("Invalid hexadecimal id in directory cache")]
|
||||||
BadHexInCache(#[source] hex::FromHexError),
|
BadHexInCache(#[source] hex::FromHexError),
|
||||||
/// An error given by the network document crate.
|
/// An error given by the network document crate.
|
||||||
#[error("netdoc error: {0}")]
|
#[error("netdoc error from {source}: {cause}")]
|
||||||
NetDocError(#[from] tor_netdoc::Error),
|
NetDocError {
|
||||||
|
/// Where the document came from.
|
||||||
|
source: DocSource,
|
||||||
|
/// What error we got.
|
||||||
|
#[source]
|
||||||
|
cause: tor_netdoc::Error,
|
||||||
|
},
|
||||||
/// An error given by dirclient
|
/// An error given by dirclient
|
||||||
#[error("dirclient error: {0}")]
|
#[error("dirclient error: {0}")]
|
||||||
DirClientError(#[from] tor_dirclient::Error),
|
DirClientError(#[from] tor_dirclient::Error),
|
||||||
|
@ -109,6 +116,13 @@ impl Error {
|
||||||
cause: Arc::new(err),
|
cause: Arc::new(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a new `Error` from `tor_netdoc::Error`.
|
||||||
|
///
|
||||||
|
/// Also takes a source so that we can keep track of where the document came from.
|
||||||
|
pub(crate) fn from_netdoc(source: DocSource, cause: tor_netdoc::Error) -> Error {
|
||||||
|
Error::NetDocError { source, cause }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<rusqlite::Error> for Error {
|
impl From<rusqlite::Error> for Error {
|
||||||
|
@ -143,7 +157,10 @@ impl HasKind for Error {
|
||||||
E::CantAdvanceState => EK::DirectoryStalled,
|
E::CantAdvanceState => EK::DirectoryStalled,
|
||||||
E::StorageError(_) => EK::CacheAccessFailed,
|
E::StorageError(_) => EK::CacheAccessFailed,
|
||||||
E::ConsensusDiffError(_) => EK::TorProtocolViolation,
|
E::ConsensusDiffError(_) => EK::TorProtocolViolation,
|
||||||
E::NetDocError(_) => todo!(), // TODO: depends on source
|
E::NetDocError { source, .. } => match source {
|
||||||
|
DocSource::LocalCache => EK::CacheCorrupted,
|
||||||
|
DocSource::DirServer { .. } => EK::TorProtocolViolation,
|
||||||
|
},
|
||||||
E::DirClientError(e) => e.kind(),
|
E::DirClientError(e) => e.kind(),
|
||||||
E::SignatureError(_) => EK::TorProtocolViolation,
|
E::SignatureError(_) => EK::TorProtocolViolation,
|
||||||
E::IOError(_) => EK::CacheAccessFailed,
|
E::IOError(_) => EK::CacheAccessFailed,
|
||||||
|
|
|
@ -181,6 +181,24 @@ impl<'a> BoolResetter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The possible origins of a document.
|
||||||
|
///
|
||||||
|
/// Used (for example) to report where we got a document from if it fails to
|
||||||
|
/// parse.
|
||||||
|
#[derive(Debug, Clone, derive_more::Display)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum DocSource {
|
||||||
|
/// We loaded the document from our cache.
|
||||||
|
#[display(fmt = "local cache")]
|
||||||
|
LocalCache,
|
||||||
|
/// We fetched the document from a server.
|
||||||
|
//
|
||||||
|
// TODO: we'll should add a lot more information here in the future, once
|
||||||
|
// it's available from tor_dirclient::DirSource,
|
||||||
|
#[display(fmt = "directory server")]
|
||||||
|
DirServer {},
|
||||||
|
}
|
||||||
|
|
||||||
impl<R: Runtime> DirMgr<R> {
|
impl<R: Runtime> DirMgr<R> {
|
||||||
/// Try to load the directory from disk, without launching any
|
/// Try to load the directory from disk, without launching any
|
||||||
/// kind of update process.
|
/// kind of update process.
|
||||||
|
|
|
@ -22,7 +22,6 @@ use tor_netdoc::doc::netstatus::Lifetime;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::event::{DirStatus, DirStatusInner};
|
use crate::event::{DirStatus, DirStatusInner};
|
||||||
use crate::DirEvent;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
docmeta::{AuthCertMeta, ConsensusMeta},
|
docmeta::{AuthCertMeta, ConsensusMeta},
|
||||||
retry::DownloadSchedule,
|
retry::DownloadSchedule,
|
||||||
|
@ -31,6 +30,7 @@ use crate::{
|
||||||
CacheUsage, ClientRequest, DirMgrConfig, DirState, DocId, DocumentText, Error, Readiness,
|
CacheUsage, ClientRequest, DirMgrConfig, DirState, DocId, DocumentText, Error, Readiness,
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
use crate::{DirEvent, DocSource};
|
||||||
use tor_checkable::{ExternallySigned, SelfSigned, Timebound};
|
use tor_checkable::{ExternallySigned, SelfSigned, Timebound};
|
||||||
use tor_llcrypto::pk::rsa::RsaIdentity;
|
use tor_llcrypto::pk::rsa::RsaIdentity;
|
||||||
use tor_netdoc::doc::{
|
use tor_netdoc::doc::{
|
||||||
|
@ -229,7 +229,8 @@ impl<DM: WriteNetDir> DirState for GetConsensusState<DM> {
|
||||||
_ => return Err(Error::Unwanted("Not an md consensus")),
|
_ => return Err(Error::Unwanted("Not an md consensus")),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add_consensus_text(true, text.as_str().map_err(Error::BadUtf8InCache)?)
|
let source = DocSource::LocalCache;
|
||||||
|
self.add_consensus_text(source, text.as_str().map_err(Error::BadUtf8InCache)?)
|
||||||
.map(|meta| meta.is_some())
|
.map(|meta| meta.is_some())
|
||||||
}
|
}
|
||||||
fn add_from_download(
|
fn add_from_download(
|
||||||
|
@ -238,7 +239,8 @@ impl<DM: WriteNetDir> DirState for GetConsensusState<DM> {
|
||||||
_request: &ClientRequest,
|
_request: &ClientRequest,
|
||||||
storage: Option<&Mutex<SqliteStore>>,
|
storage: Option<&Mutex<SqliteStore>>,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
if let Some(meta) = self.add_consensus_text(false, text)? {
|
let source = DocSource::DirServer {};
|
||||||
|
if let Some(meta) = self.add_consensus_text(source, text)? {
|
||||||
if let Some(store) = storage {
|
if let Some(store) = storage {
|
||||||
let mut w = store.lock().expect("Directory storage lock poisoned");
|
let mut w = store.lock().expect("Directory storage lock poisoned");
|
||||||
w.store_consensus(meta, ConsensusFlavor::Microdesc, true, text)?;
|
w.store_consensus(meta, ConsensusFlavor::Microdesc, true, text)?;
|
||||||
|
@ -268,12 +270,13 @@ impl<DM: WriteNetDir> GetConsensusState<DM> {
|
||||||
/// correct, or if it is ill-formed.
|
/// correct, or if it is ill-formed.
|
||||||
fn add_consensus_text(
|
fn add_consensus_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
from_cache: bool,
|
source: DocSource,
|
||||||
text: &str,
|
text: &str,
|
||||||
) -> Result<Option<&ConsensusMeta>> {
|
) -> Result<Option<&ConsensusMeta>> {
|
||||||
// Try to parse it and get its metadata.
|
// Try to parse it and get its metadata.
|
||||||
let (consensus_meta, unvalidated) = {
|
let (consensus_meta, unvalidated) = {
|
||||||
let (signedval, remainder, parsed) = MdConsensus::parse(text)?;
|
let (signedval, remainder, parsed) =
|
||||||
|
MdConsensus::parse(text).map_err(|e| Error::from_netdoc(source.clone(), e))?;
|
||||||
let now = current_time(&self.writedir)?;
|
let now = current_time(&self.writedir)?;
|
||||||
if let Ok(timely) = parsed.check_valid_at(&now) {
|
if let Ok(timely) = parsed.check_valid_at(&now) {
|
||||||
let meta = ConsensusMeta::from_unvalidated(signedval, remainder, &timely);
|
let meta = ConsensusMeta::from_unvalidated(signedval, remainder, &timely);
|
||||||
|
@ -303,7 +306,7 @@ impl<DM: WriteNetDir> GetConsensusState<DM> {
|
||||||
|
|
||||||
self.next = Some(GetCertsState {
|
self.next = Some(GetCertsState {
|
||||||
cache_usage: self.cache_usage,
|
cache_usage: self.cache_usage,
|
||||||
from_cache,
|
consensus_source: source,
|
||||||
unvalidated,
|
unvalidated,
|
||||||
consensus_meta,
|
consensus_meta,
|
||||||
missing_certs: desired_certs,
|
missing_certs: desired_certs,
|
||||||
|
@ -334,9 +337,8 @@ impl<DM: WriteNetDir> GetConsensusState<DM> {
|
||||||
struct GetCertsState<DM: WriteNetDir> {
|
struct GetCertsState<DM: WriteNetDir> {
|
||||||
/// The cache usage we had in mind when we began. Used to reset.
|
/// The cache usage we had in mind when we began. Used to reset.
|
||||||
cache_usage: CacheUsage,
|
cache_usage: CacheUsage,
|
||||||
/// True iff we loaded the consensus from our cache.
|
/// Where did we get our consensus?
|
||||||
#[allow(dead_code)]
|
consensus_source: DocSource,
|
||||||
from_cache: bool,
|
|
||||||
/// The consensus that we are trying to validate.
|
/// The consensus that we are trying to validate.
|
||||||
unvalidated: UnvalidatedMdConsensus,
|
unvalidated: UnvalidatedMdConsensus,
|
||||||
/// Metadata for the consensus.
|
/// Metadata for the consensus.
|
||||||
|
@ -398,7 +400,9 @@ impl<DM: WriteNetDir> DirState for GetCertsState<DM> {
|
||||||
// our input and remembering them.
|
// our input and remembering them.
|
||||||
for id in &self.missing_docs() {
|
for id in &self.missing_docs() {
|
||||||
if let Some(cert) = docs.get(id) {
|
if let Some(cert) = docs.get(id) {
|
||||||
let parsed = AuthCert::parse(cert.as_str().map_err(Error::BadUtf8InCache)?)?
|
let text = cert.as_str().map_err(Error::BadUtf8InCache)?;
|
||||||
|
let parsed = AuthCert::parse(text)
|
||||||
|
.map_err(|e| Error::from_netdoc(DocSource::LocalCache, e))?
|
||||||
.check_signature()?;
|
.check_signature()?;
|
||||||
let now = current_time(&self.writedir)?;
|
let now = current_time(&self.writedir)?;
|
||||||
if let Ok(cert) = parsed.check_valid_at(&now) {
|
if let Ok(cert) = parsed.check_valid_at(&now) {
|
||||||
|
@ -482,7 +486,11 @@ impl<DM: WriteNetDir> DirState for GetCertsState<DM> {
|
||||||
}
|
}
|
||||||
fn advance(self: Box<Self>) -> Result<Box<dyn DirState>> {
|
fn advance(self: Box<Self>) -> Result<Box<dyn DirState>> {
|
||||||
if self.can_advance() {
|
if self.can_advance() {
|
||||||
let validated = self.unvalidated.check_signature(&self.certs[..])?;
|
let consensus_source = self.consensus_source.clone();
|
||||||
|
let validated = self
|
||||||
|
.unvalidated
|
||||||
|
.check_signature(&self.certs[..])
|
||||||
|
.map_err(|e| Error::from_netdoc(consensus_source, e))?;
|
||||||
Ok(Box::new(GetMicrodescsState::new(
|
Ok(Box::new(GetMicrodescsState::new(
|
||||||
self.cache_usage,
|
self.cache_usage,
|
||||||
validated,
|
validated,
|
||||||
|
@ -1086,7 +1094,7 @@ mod test {
|
||||||
let req = tor_dirclient::request::ConsensusRequest::new(ConsensusFlavor::Microdesc);
|
let req = tor_dirclient::request::ConsensusRequest::new(ConsensusFlavor::Microdesc);
|
||||||
let req = crate::docid::ClientRequest::Consensus(req);
|
let req = crate::docid::ClientRequest::Consensus(req);
|
||||||
let outcome = state.add_from_download("this isn't a consensus", &req, Some(&store));
|
let outcome = state.add_from_download("this isn't a consensus", &req, Some(&store));
|
||||||
assert!(matches!(outcome, Err(Error::NetDocError(_))));
|
assert!(matches!(outcome, Err(Error::NetDocError { .. })));
|
||||||
// make sure it wasn't stored...
|
// make sure it wasn't stored...
|
||||||
assert!(store
|
assert!(store
|
||||||
.lock()
|
.lock()
|
||||||
|
|
|
@ -704,7 +704,8 @@ fn cmeta_from_row(row: &rusqlite::Row<'_>) -> Result<ConsensusMeta> {
|
||||||
let vu: OffsetDateTime = row.get(2)?;
|
let vu: OffsetDateTime = row.get(2)?;
|
||||||
let d_signed: String = row.get(3)?;
|
let d_signed: String = row.get(3)?;
|
||||||
let d_all: String = row.get(4)?;
|
let d_all: String = row.get(4)?;
|
||||||
let lifetime = Lifetime::new(va.into(), fu.into(), vu.into())?;
|
let lifetime = Lifetime::new(va.into(), fu.into(), vu.into())
|
||||||
|
.map_err(|_| Error::CacheCorruption("inconsistent lifetime in database"))?;
|
||||||
Ok(ConsensusMeta::new(
|
Ok(ConsensusMeta::new(
|
||||||
lifetime,
|
lifetime,
|
||||||
digest_from_hex(&d_signed)?,
|
digest_from_hex(&d_signed)?,
|
||||||
|
|
Loading…
Reference in New Issue