Loading Cargo.lock +27 −0 Original line number Diff line number Diff line Loading @@ -211,6 +211,10 @@ dependencies = [ "rlimit", "serde", "tokio", "tor-checkable", "tor-dirmgr", "tor-error", "tor-netdoc", "tor-rtcompat", "tracing", "tracing-appender", Loading Loading @@ -3515,6 +3519,8 @@ dependencies = [ "tor-error", "tor-llcrypto", "tor-protover", "visibility", "visible", "weak-table", ] Loading Loading @@ -3866,6 +3872,27 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "visibility" version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8881d5cc0ae34e3db2f1de5af81e5117a420d2f937506c2dc20d6f4cfb069051" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "visible" version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a044005fd5c0fc1ebd79c622e5606431c6b879a6a19acafb754be9926a2de73e" dependencies = [ "quote", "syn", ] [[package]] name = "void" version = "1.0.2" Loading crates/arti-testing/Cargo.toml +12 −1 Original line number Diff line number Diff line Loading @@ -15,8 +15,19 @@ publish = false [dependencies] arti = { package = "arti", path = "../arti", version = "0.1.0" } arti-client = { package = "arti-client", path = "../arti-client", version = "0.1.0" } arti-client = { package = "arti-client", path = "../arti-client", version = "0.1.0", features = [ "dirfilter", ] } tor-dirmgr = { package = "tor-dirmgr", path = "../tor-dirmgr", version = "0.1.0", features = [ "dirfilter", ] } tor-netdoc = { package = "tor-netdoc", path = "../tor-netdoc", version = "0.1.0", features = [ "experimental-api", "dangerous-expose-struct-fields", ] } tor-checkable = { path = "../tor-checkable", version = "0.1.0", features = ["experimental-api"] } tor-rtcompat = { path = "../tor-rtcompat", version = "0.1.0" } tor-error = { path = "../tor-error", version = "0.1.0" } arti-config = { path = "../arti-config", version = "0.1.0" } anyhow = "1.0.23" Loading crates/arti-testing/src/config.rs +14 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,13 @@ pub(crate) fn parse_cmdline() -> Result<Job> { .value_name("SECS") .global(true), ) .arg( Arg::with_name("dir-filter") .long("dir-filter") .takes_value(true) .value_name("FILTER_NAME") .global(true), ) .subcommand( SubCommand::with_name("connect") .about("Try to bootstrap and connect to an address") Loading Loading @@ -169,6 +176,12 @@ pub(crate) fn parse_cmdline() -> Result<Job> { } }; let dir_filter = matches .value_of("dir-filter") .map(crate::dirfilter::new_filter) .transpose()? .unwrap_or_else(crate::dirfilter::nil_filter); let action = if let Some(_m) = matches.subcommand_matches("bootstrap") { Action::Bootstrap } else if let Some(matches) = matches.subcommand_matches("connect") { Loading @@ -191,6 +204,7 @@ pub(crate) fn parse_cmdline() -> Result<Job> { config, timeout, tcp_breakage, dir_filter, console_log, expectation, }) Loading crates/arti-testing/src/dirfilter.rs 0 → 100644 +193 −0 Original line number Diff line number Diff line //! Support for modifying directories in various ways in order to cause //! different kinds of network failure. use anyhow::{anyhow, Result}; use rand::Rng; use std::sync::{Arc, Mutex}; use tor_dirmgr::filter::DirFilter; use tor_netdoc::{ doc::{ microdesc::Microdesc, netstatus::{RouterStatus, UncheckedMdConsensus}, }, types::{family::RelayFamily, policy::PortPolicy}, }; /// Return a new directory filter as configured by a specified string. pub(crate) fn new_filter(s: &str) -> Result<Arc<dyn DirFilter + 'static>> { Ok(match s { "replace-onion-keys" => Arc::new(ReplaceOnionKeysFilter::default()), "one-big-family" => Arc::new(OneBigFamilyFilter::default()), "no-exit-ports" => Arc::new(NoExitPortsFilter::default()), "bad-signatures" => Arc::new(BadSignaturesFilter::default()), "non-existent-signing-keys" => Arc::new(NonexistentSigningKeysFilter::default()), "bad-microdesc-digests" => Arc::new(BadMicrodescDigestsFilter::default()), _ => { return Err(anyhow!( "Unrecognized filter. Options are: replace-onion-keys, one-big-family, no-exit-ports, bad-signatures, non-existent-signing-keys, bad-microdesc-digests." )); } }) } /// A filter that doesn't do anything. /// /// We define this so we can set a filter unconditionally and simplify our code a /// little. #[derive(Debug)] struct NilFilter; impl DirFilter for NilFilter {} /// Return a filter that doesn't do anything. pub(crate) fn nil_filter() -> Arc<dyn DirFilter + 'static> { Arc::new(NilFilter) } /// A filter to replace onion keys with junk. /// /// Doing this means that all CREATE2 attempts via ntor will fail. (If any were /// to succeed, they'd fail when they try to extend.) #[derive(Debug, Default)] struct ReplaceOnionKeysFilter; impl DirFilter for ReplaceOnionKeysFilter { fn filter_md(&self, mut md: Microdesc) -> tor_dirmgr::Result<Microdesc> { let junk_key: [u8; 32] = rand::thread_rng().gen(); md.ntor_onion_key = junk_key.into(); Ok(md) } } /// A filter to put all relays into a family with one another. /// /// This filter will prevent the client from generating any mult-hop circuits, /// since they'll all violate our path constraints. #[derive(Debug, Default)] struct OneBigFamilyFilter { /// The family we're going to put all the microdescs into. We set this to /// contain all the identities, every time we load a consensus. /// /// (This filter won't do a very good job of ensuring consistency between /// this family and the MDs we attach it to, but that's okay for the kind of /// testing we want to do.) new_family: Mutex<Arc<RelayFamily>>, } impl DirFilter for OneBigFamilyFilter { fn filter_consensus( &self, consensus: UncheckedMdConsensus, ) -> tor_dirmgr::Result<UncheckedMdConsensus> { let mut new_family = RelayFamily::new(); for r in consensus.dangerously_peek().consensus.relays() { new_family.push(*r.rsa_identity()); } *self.new_family.lock().expect("poisoned lock") = Arc::new(new_family); Ok(consensus) } fn filter_md(&self, mut md: Microdesc) -> tor_dirmgr::Result<Microdesc> { let big_family = self.new_family.lock().expect("poisoned lock").clone(); md.family = big_family; Ok(md) } } /// A filter to remove all exit policies. /// /// With this change, any attempt to build a circuit connecting for to an /// address will fail, since no exit will appear to support it. #[derive(Debug)] struct NoExitPortsFilter { /// A "reject all ports" policy. reject_all: Arc<PortPolicy>, } impl Default for NoExitPortsFilter { fn default() -> Self { Self { reject_all: Arc::new(PortPolicy::new_reject_all()), } } } impl DirFilter for NoExitPortsFilter { fn filter_md(&self, mut md: Microdesc) -> tor_dirmgr::Result<Microdesc> { md.ipv4_policy = self.reject_all.clone(); md.ipv6_policy = self.reject_all.clone(); Ok(md) } } /// A filter to replace the signatures on a consensus with invalid ones. /// /// This change will cause directory validation to fail: we'll get good /// certificates and discover that our directory is invalid. #[derive(Debug, Default)] struct BadSignaturesFilter; impl DirFilter for BadSignaturesFilter { fn filter_consensus( &self, consensus: UncheckedMdConsensus, ) -> tor_dirmgr::Result<UncheckedMdConsensus> { let (mut consensus, time_bounds) = consensus.dangerously_into_parts(); // We retain the signatures, but change the declared digest of the // document. This will make all the signatures invalid. consensus.siggroup.sha1 = Some(*b"can you reverse sha1"); consensus.siggroup.sha256 = Some(*b"sha256 preimage is harder so far"); Ok(UncheckedMdConsensus::new(consensus, time_bounds)) } } /// A filter that (nastily) claims all the authorities have changed their /// signing keys. /// /// This change will make us go looking for a set of certificates that don't /// exist so that we can verify the consensus. #[derive(Debug, Default)] struct NonexistentSigningKeysFilter; impl DirFilter for NonexistentSigningKeysFilter { fn filter_consensus( &self, consensus: UncheckedMdConsensus, ) -> tor_dirmgr::Result<UncheckedMdConsensus> { let (mut consensus, time_bounds) = consensus.dangerously_into_parts(); let mut rng = rand::thread_rng(); for signature in consensus.siggroup.signatures.iter_mut() { let sk_fingerprint: [u8; 20] = rng.gen(); signature.key_ids.sk_fingerprint = sk_fingerprint.into(); } Ok(UncheckedMdConsensus::new(consensus, time_bounds)) } } /// A filter that replaces all the microdesc digests with ones that don't exist. /// /// This filter will let us validate the consensus, but we'll look forever for /// valid the microdescriptors it claims are present. #[derive(Debug, Default)] struct BadMicrodescDigestsFilter; impl DirFilter for BadMicrodescDigestsFilter { fn filter_consensus( &self, consensus: UncheckedMdConsensus, ) -> tor_dirmgr::Result<UncheckedMdConsensus> { let (mut consensus, time_bounds) = consensus.dangerously_into_parts(); let mut rng = rand::thread_rng(); for rs in consensus.consensus.relays.iter_mut() { rs.rs.doc_digest = rng.gen(); } Ok(UncheckedMdConsensus::new(consensus, time_bounds)) } } crates/arti-testing/src/main.rs +6 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ #![allow(clippy::print_stdout)] // Allowed in this crate only. mod config; mod dirfilter; mod rt; mod traces; Loading @@ -87,6 +88,7 @@ use arti::ArtiConfig; use arti_client::TorClient; use futures::task::SpawnExt; use rt::badtcp::BrokenTcpProvider; use tor_dirmgr::filter::DirFilter; use tor_rtcompat::{PreferredRuntime, Runtime, SleepProviderExt}; use anyhow::{anyhow, Result}; Loading Loading @@ -201,6 +203,9 @@ struct Job { /// Describes how (if at all) to break the TCP connections. tcp_breakage: TcpBreakage, /// Describes how (if at all) to mess with directories. dir_filter: Arc<dyn DirFilter + 'static>, /// The tracing configuration for our console log. console_log: String, Loading @@ -220,6 +225,7 @@ impl Job { let config: ArtiConfig = self.config.load()?.try_into()?; let client = TorClient::with_runtime(runtime) .config(config.tor_client_config()?) .dirfilter(self.dir_filter.clone()) .create_unbootstrapped()?; Ok(client) } Loading Loading
Cargo.lock +27 −0 Original line number Diff line number Diff line Loading @@ -211,6 +211,10 @@ dependencies = [ "rlimit", "serde", "tokio", "tor-checkable", "tor-dirmgr", "tor-error", "tor-netdoc", "tor-rtcompat", "tracing", "tracing-appender", Loading Loading @@ -3515,6 +3519,8 @@ dependencies = [ "tor-error", "tor-llcrypto", "tor-protover", "visibility", "visible", "weak-table", ] Loading Loading @@ -3866,6 +3872,27 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "visibility" version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8881d5cc0ae34e3db2f1de5af81e5117a420d2f937506c2dc20d6f4cfb069051" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "visible" version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a044005fd5c0fc1ebd79c622e5606431c6b879a6a19acafb754be9926a2de73e" dependencies = [ "quote", "syn", ] [[package]] name = "void" version = "1.0.2" Loading
crates/arti-testing/Cargo.toml +12 −1 Original line number Diff line number Diff line Loading @@ -15,8 +15,19 @@ publish = false [dependencies] arti = { package = "arti", path = "../arti", version = "0.1.0" } arti-client = { package = "arti-client", path = "../arti-client", version = "0.1.0" } arti-client = { package = "arti-client", path = "../arti-client", version = "0.1.0", features = [ "dirfilter", ] } tor-dirmgr = { package = "tor-dirmgr", path = "../tor-dirmgr", version = "0.1.0", features = [ "dirfilter", ] } tor-netdoc = { package = "tor-netdoc", path = "../tor-netdoc", version = "0.1.0", features = [ "experimental-api", "dangerous-expose-struct-fields", ] } tor-checkable = { path = "../tor-checkable", version = "0.1.0", features = ["experimental-api"] } tor-rtcompat = { path = "../tor-rtcompat", version = "0.1.0" } tor-error = { path = "../tor-error", version = "0.1.0" } arti-config = { path = "../arti-config", version = "0.1.0" } anyhow = "1.0.23" Loading
crates/arti-testing/src/config.rs +14 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,13 @@ pub(crate) fn parse_cmdline() -> Result<Job> { .value_name("SECS") .global(true), ) .arg( Arg::with_name("dir-filter") .long("dir-filter") .takes_value(true) .value_name("FILTER_NAME") .global(true), ) .subcommand( SubCommand::with_name("connect") .about("Try to bootstrap and connect to an address") Loading Loading @@ -169,6 +176,12 @@ pub(crate) fn parse_cmdline() -> Result<Job> { } }; let dir_filter = matches .value_of("dir-filter") .map(crate::dirfilter::new_filter) .transpose()? .unwrap_or_else(crate::dirfilter::nil_filter); let action = if let Some(_m) = matches.subcommand_matches("bootstrap") { Action::Bootstrap } else if let Some(matches) = matches.subcommand_matches("connect") { Loading @@ -191,6 +204,7 @@ pub(crate) fn parse_cmdline() -> Result<Job> { config, timeout, tcp_breakage, dir_filter, console_log, expectation, }) Loading
crates/arti-testing/src/dirfilter.rs 0 → 100644 +193 −0 Original line number Diff line number Diff line //! Support for modifying directories in various ways in order to cause //! different kinds of network failure. use anyhow::{anyhow, Result}; use rand::Rng; use std::sync::{Arc, Mutex}; use tor_dirmgr::filter::DirFilter; use tor_netdoc::{ doc::{ microdesc::Microdesc, netstatus::{RouterStatus, UncheckedMdConsensus}, }, types::{family::RelayFamily, policy::PortPolicy}, }; /// Return a new directory filter as configured by a specified string. pub(crate) fn new_filter(s: &str) -> Result<Arc<dyn DirFilter + 'static>> { Ok(match s { "replace-onion-keys" => Arc::new(ReplaceOnionKeysFilter::default()), "one-big-family" => Arc::new(OneBigFamilyFilter::default()), "no-exit-ports" => Arc::new(NoExitPortsFilter::default()), "bad-signatures" => Arc::new(BadSignaturesFilter::default()), "non-existent-signing-keys" => Arc::new(NonexistentSigningKeysFilter::default()), "bad-microdesc-digests" => Arc::new(BadMicrodescDigestsFilter::default()), _ => { return Err(anyhow!( "Unrecognized filter. Options are: replace-onion-keys, one-big-family, no-exit-ports, bad-signatures, non-existent-signing-keys, bad-microdesc-digests." )); } }) } /// A filter that doesn't do anything. /// /// We define this so we can set a filter unconditionally and simplify our code a /// little. #[derive(Debug)] struct NilFilter; impl DirFilter for NilFilter {} /// Return a filter that doesn't do anything. pub(crate) fn nil_filter() -> Arc<dyn DirFilter + 'static> { Arc::new(NilFilter) } /// A filter to replace onion keys with junk. /// /// Doing this means that all CREATE2 attempts via ntor will fail. (If any were /// to succeed, they'd fail when they try to extend.) #[derive(Debug, Default)] struct ReplaceOnionKeysFilter; impl DirFilter for ReplaceOnionKeysFilter { fn filter_md(&self, mut md: Microdesc) -> tor_dirmgr::Result<Microdesc> { let junk_key: [u8; 32] = rand::thread_rng().gen(); md.ntor_onion_key = junk_key.into(); Ok(md) } } /// A filter to put all relays into a family with one another. /// /// This filter will prevent the client from generating any mult-hop circuits, /// since they'll all violate our path constraints. #[derive(Debug, Default)] struct OneBigFamilyFilter { /// The family we're going to put all the microdescs into. We set this to /// contain all the identities, every time we load a consensus. /// /// (This filter won't do a very good job of ensuring consistency between /// this family and the MDs we attach it to, but that's okay for the kind of /// testing we want to do.) new_family: Mutex<Arc<RelayFamily>>, } impl DirFilter for OneBigFamilyFilter { fn filter_consensus( &self, consensus: UncheckedMdConsensus, ) -> tor_dirmgr::Result<UncheckedMdConsensus> { let mut new_family = RelayFamily::new(); for r in consensus.dangerously_peek().consensus.relays() { new_family.push(*r.rsa_identity()); } *self.new_family.lock().expect("poisoned lock") = Arc::new(new_family); Ok(consensus) } fn filter_md(&self, mut md: Microdesc) -> tor_dirmgr::Result<Microdesc> { let big_family = self.new_family.lock().expect("poisoned lock").clone(); md.family = big_family; Ok(md) } } /// A filter to remove all exit policies. /// /// With this change, any attempt to build a circuit connecting for to an /// address will fail, since no exit will appear to support it. #[derive(Debug)] struct NoExitPortsFilter { /// A "reject all ports" policy. reject_all: Arc<PortPolicy>, } impl Default for NoExitPortsFilter { fn default() -> Self { Self { reject_all: Arc::new(PortPolicy::new_reject_all()), } } } impl DirFilter for NoExitPortsFilter { fn filter_md(&self, mut md: Microdesc) -> tor_dirmgr::Result<Microdesc> { md.ipv4_policy = self.reject_all.clone(); md.ipv6_policy = self.reject_all.clone(); Ok(md) } } /// A filter to replace the signatures on a consensus with invalid ones. /// /// This change will cause directory validation to fail: we'll get good /// certificates and discover that our directory is invalid. #[derive(Debug, Default)] struct BadSignaturesFilter; impl DirFilter for BadSignaturesFilter { fn filter_consensus( &self, consensus: UncheckedMdConsensus, ) -> tor_dirmgr::Result<UncheckedMdConsensus> { let (mut consensus, time_bounds) = consensus.dangerously_into_parts(); // We retain the signatures, but change the declared digest of the // document. This will make all the signatures invalid. consensus.siggroup.sha1 = Some(*b"can you reverse sha1"); consensus.siggroup.sha256 = Some(*b"sha256 preimage is harder so far"); Ok(UncheckedMdConsensus::new(consensus, time_bounds)) } } /// A filter that (nastily) claims all the authorities have changed their /// signing keys. /// /// This change will make us go looking for a set of certificates that don't /// exist so that we can verify the consensus. #[derive(Debug, Default)] struct NonexistentSigningKeysFilter; impl DirFilter for NonexistentSigningKeysFilter { fn filter_consensus( &self, consensus: UncheckedMdConsensus, ) -> tor_dirmgr::Result<UncheckedMdConsensus> { let (mut consensus, time_bounds) = consensus.dangerously_into_parts(); let mut rng = rand::thread_rng(); for signature in consensus.siggroup.signatures.iter_mut() { let sk_fingerprint: [u8; 20] = rng.gen(); signature.key_ids.sk_fingerprint = sk_fingerprint.into(); } Ok(UncheckedMdConsensus::new(consensus, time_bounds)) } } /// A filter that replaces all the microdesc digests with ones that don't exist. /// /// This filter will let us validate the consensus, but we'll look forever for /// valid the microdescriptors it claims are present. #[derive(Debug, Default)] struct BadMicrodescDigestsFilter; impl DirFilter for BadMicrodescDigestsFilter { fn filter_consensus( &self, consensus: UncheckedMdConsensus, ) -> tor_dirmgr::Result<UncheckedMdConsensus> { let (mut consensus, time_bounds) = consensus.dangerously_into_parts(); let mut rng = rand::thread_rng(); for rs in consensus.consensus.relays.iter_mut() { rs.rs.doc_digest = rng.gen(); } Ok(UncheckedMdConsensus::new(consensus, time_bounds)) } }
crates/arti-testing/src/main.rs +6 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ #![allow(clippy::print_stdout)] // Allowed in this crate only. mod config; mod dirfilter; mod rt; mod traces; Loading @@ -87,6 +88,7 @@ use arti::ArtiConfig; use arti_client::TorClient; use futures::task::SpawnExt; use rt::badtcp::BrokenTcpProvider; use tor_dirmgr::filter::DirFilter; use tor_rtcompat::{PreferredRuntime, Runtime, SleepProviderExt}; use anyhow::{anyhow, Result}; Loading Loading @@ -201,6 +203,9 @@ struct Job { /// Describes how (if at all) to break the TCP connections. tcp_breakage: TcpBreakage, /// Describes how (if at all) to mess with directories. dir_filter: Arc<dyn DirFilter + 'static>, /// The tracing configuration for our console log. console_log: String, Loading @@ -220,6 +225,7 @@ impl Job { let config: ArtiConfig = self.config.load()?.try_into()?; let client = TorClient::with_runtime(runtime) .config(config.tor_client_config()?) .dirfilter(self.dir_filter.clone()) .create_unbootstrapped()?; Ok(client) } Loading