Merge branch 'main' into 'abstract-pt-mgr'

# Conflicts:
#   crates/tor-chanmgr/src/factory.rs
This commit is contained in:
Nick Mathewson 2022-11-22 18:12:25 +00:00
commit 09092394a8
54 changed files with 1338 additions and 339 deletions

View File

@ -91,7 +91,9 @@ rust-latest-async-std-rustls:
rust-nightly:
stage: test
image: rustlang/rust:nightly
#image: rustlang/rust:nightly
# Temporary workarounds for arti#633.
image: rustlang/rust@sha256:415b7c22ab4a8a3ec3efc9cc8d7b018964f0c6757fff27bbd110e0ed92566321
allow_failure: true
script:
- rustup show

4
Cargo.lock generated
View File

@ -3528,6 +3528,7 @@ dependencies = [
"postage",
"rand 0.8.5",
"rand_chacha 0.3.1",
"serde",
"slab",
"thiserror",
"tokio",
@ -3851,9 +3852,12 @@ dependencies = [
"postage",
"rand 0.8.5",
"retain_mut",
"safelog",
"serde",
"serde_json",
"strum",
"thiserror",
"toml",
"tor-basic-utils",
"tor-config",
"tor-error",

View File

@ -19,10 +19,21 @@ default = ["tokio", "native-tls", "compression"]
# * Features which are select a particular implementation or build flag and
# which therefore are not strictly additive.
# * Features which may introduce unnecessary licensing restrictions.
full = ["tokio", "async-std", "native-tls", "compression", "tor-rtcompat/full", "tor-proto/full", "tor-netdoc/full", "tor-dirmgr/full"]
full = [
"tokio",
"async-std",
"native-tls",
"compression",
"bridge-client",
"pt-client",
"tor-rtcompat/full",
"tor-proto/full",
"tor-netdoc/full",
"tor-dirmgr/full",
]
async-std = ["tor-rtcompat/async-std"]
bridge-client = ["tor-guardmgr/bridge-client"]
bridge-client = ["tor-guardmgr/bridge-client", "tor-dirmgr/bridge-client"]
tokio = ["tor-rtcompat/tokio", "tor-proto/tokio"]
native-tls = ["tor-rtcompat/native-tls"]
pt-client = ["bridge-client", "tor-guardmgr/pt-client"]
@ -42,11 +53,9 @@ compression = ["tor-dirmgr/compression"]
# These APIs are not covered by semantic versioning. Using this
# feature voids your "semver warrantee".
experimental = [
"bridge-client",
"dirfilter",
"experimental-api",
"error_detail",
"pt-client",
"tor-proto/experimental",
"tor-cell/experimental",
"tor-checkable/experimental",
@ -80,7 +89,9 @@ tor-chanmgr = { path = "../tor-chanmgr", version = "0.7.0" }
tor-checkable = { path = "../tor-checkable", version = "0.3.0" }
tor-circmgr = { path = "../tor-circmgr", version = "0.6.0" }
tor-config = { path = "../tor-config", version = "0.6.0" }
tor-dirmgr = { path = "../tor-dirmgr", version = "0.8.0", default-features = false, features = ["mmap"] }
tor-dirmgr = { path = "../tor-dirmgr", version = "0.8.0", default-features = false, features = [
"mmap",
] }
tor-error = { path = "../tor-error", version = "0.3.2" }
tor-guardmgr = { path = "../tor-guardmgr", version = "0.7.0" }
tor-llcrypto = { path = "../tor-llcrypto", version = "0.3.5" }

View File

@ -161,6 +161,8 @@ about these features.
* `async-std` -- build with [async-std](https://async.rs/) support
* `compression` (default) -- Build support for downloading compressed
documents. Requires a C compiler.
* `bridge-client` -- Build with support for bridges.
* `pt-client` -- Build with support for pluggable transports.
* `full` -- Build with all features above, along with all stable additive
features from other arti crates. (This does not include experimental
@ -202,8 +204,6 @@ implementation with another.
versioning[^1] guarantees: we might break them or remove them between patch
versions.
* `bridge-client` -- Build with (as yet unimplemented) support for bridges
* `pt-client` -- Build with (as yet unimplemented) support for pluggable transports
* `experimental-api` -- build with experimental, unstable API support.
* `error_detail` -- expose the `arti_client::Error` inner error type.
* `dirfilter` -- expose the `DirFilter` API, which lets you modify a network

View File

@ -490,7 +490,7 @@ mod test {
"www.example.com:8000",
);
check(
&TorAddr::from(("www.example.com", 8000)).unwrap(),
TorAddr::from(("www.example.com", 8000)).unwrap(),
"www.example.com:8000",
);
check("[2001:db8::0042]:9001".to_owned(), "[2001:db8::42]:9001");

View File

@ -18,6 +18,8 @@ pub use tor_config::{define_list_builder_accessors, define_list_builder_helper};
pub use tor_config::{BoolOrAuto, ConfigError};
pub use tor_config::{CfgPath, CfgPathError, ConfigBuildError, ConfigurationSource, Reconfigure};
pub use tor_guardmgr::bridge::BridgeConfigBuilder;
#[cfg(feature = "bridge-client")]
#[cfg_attr(docsrs, doc(cfg(feature = "bridge-client")))]
pub use tor_guardmgr::bridge::BridgeParseError;
@ -229,12 +231,10 @@ pub struct BridgesConfig {
/// `false` means to not use even configured bridges.
/// `true` means to insist on the use of bridges;
/// if none are configured, that's then an error.
#[cfg(feature = "bridge-client")]
#[builder(default)]
pub(crate) enabled: BoolOrAuto,
/// Configured list of bridges (possibly via pluggable transports)
#[cfg(feature = "bridge-client")]
#[builder(sub_builder, setter(custom))]
#[builder_field_attr(serde(default))]
bridges: BridgeList,
@ -256,7 +256,6 @@ define_list_builder_helper! {
default = vec![];
}
#[cfg(feature = "bridge-client")]
impl_standard_builder! { BridgesConfig }
/// Check that the bridge configuration is right
@ -309,32 +308,28 @@ impl BridgesConfig {
//
// This type alias arranges that we can put `BridgeList` in `BridgesConfig`
// and have derive_builder put a `BridgeListBuilder` in `BridgesConfigBuilder`.
#[cfg(feature = "bridge-client")]
pub type BridgeList = Vec<BridgeConfig>;
#[cfg(feature = "bridge-client")]
define_list_builder_helper! {
struct BridgeListBuilder {
bridges: [BridgeConfig],
bridges: [BridgeConfigBuilder],
}
built: BridgeList = bridges;
default = vec![];
item_build: |bridge| Ok(bridge.clone());
#[serde(try_from="MultilineListBuilder")]
#[serde(into="MultilineListBuilder")]
#[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
#[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
}
#[cfg(feature = "bridge-client")]
convert_helper_via_multi_line_list_builder! {
struct BridgeListBuilder {
bridges: [BridgeConfig],
bridges: [BridgeConfigBuilder],
}
}
#[cfg(feature = "bridge-client")]
define_list_builder_accessors! {
struct BridgesConfigBuilder {
pub bridges: [BridgeConfig],
pub bridges: [BridgeConfigBuilder],
}
}

View File

@ -12,9 +12,28 @@ categories = ["command-line-utilities", "cryptography"]
repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = ["tokio", "native-tls", "dns-proxy", "harden", "compression"]
default = [
"tokio",
"native-tls",
"dns-proxy",
"harden",
"compression",
"bridge-client",
"pt-client",
]
full = ["async-std", "tokio", "native-tls", "journald", "arti-client/full", "dns-proxy", "harden", "compression"]
full = [
"async-std",
"tokio",
"native-tls",
"journald",
"arti-client/full",
"dns-proxy",
"harden",
"compression",
"bridge-client",
"pt-client",
]
async-std = [
"arti-client/async-std",
@ -26,7 +45,7 @@ async-std = [
]
bridge-client = ["arti-client/bridge-client"]
dns-proxy = ["trust-dns-proto"]
experimental-api = ["bridge-client", "pt-client", "visibility"]
experimental-api = ["visibility"]
harden = ["secmem-proc"]
tokio = ["tokio-crate", "arti-client/tokio", "tor-rtcompat/tokio"]
native-tls = ["arti-client/native-tls", "tor-rtcompat/native-tls"]

View File

@ -123,6 +123,8 @@ work.
disabling debugger attachment and other local memory-inspection vectors.
* `compression` (default) -- Build support for downloading compressed
documents. Requires a C compiler.
* `bridge-client` (default) -- Build with support for bridges.
* `pt-client` (default) -- Build with support for pluggable transports.
* `full` -- Build with all features above, along with all stable additive
features from other arti crates. (This does not include experimental
@ -162,8 +164,6 @@ implementation with another.
versioning[^1] guarantees: we might break them or remove them between patch
versions.
* `bridge-client` -- Build with (as yet unimplemented) support for bridges
* `pt-client` -- Build with (as yet unimplemented) support for pluggable transports
* `experimental-api` -- build with experimental, unstable API support.
(Right now, most APIs in the `arti` crate are experimental, since this
crate was originally written to run as a binary only.)

View File

@ -314,9 +314,6 @@ mod test {
// which have example config, since if the feature isn't enabled,
// those keys are ignored (unrecognized).
#[cfg(not(feature = "pt-client"))]
known_unrecognized_options_new.extend(["bridges.bridges", "bridges.enabled"]);
// The unrecognized options in new are those that are only new, plus those in all
known_unrecognized_options_new.extend(known_unrecognized_options_all.clone());
@ -615,7 +612,7 @@ mod test {
let example = uncomment_example_settings(example_file);
let example: toml::Value = toml::from_str(&example).unwrap();
// dbg!(&example);
let example = serde_json::to_value(&example).unwrap();
let example = serde_json::to_value(example).unwrap();
// dbg!(&example);
// "Exhaustive" taxonomy of the recognized configuration keys
@ -626,8 +623,8 @@ mod test {
// I'm not sure this is quite perfect but it is pretty good,
// and has found a number of un-exampled config keys.
let exhausts = [
serde_json::to_value(&TorClientConfig::builder()).unwrap(),
serde_json::to_value(&ArtiConfig::builder()).unwrap(),
serde_json::to_value(TorClientConfig::builder()).unwrap(),
serde_json::to_value(ArtiConfig::builder()).unwrap(),
];
#[derive(Default, Debug)]
@ -783,6 +780,20 @@ mod test {
);
}
/// Check that the `Report` of `err` contains the string `exp`, and otherwise panic
#[cfg_attr(feature = "pt-client", allow(dead_code))]
fn expect_err_contains(err: ConfigResolveError, exp: &str) {
use std::error::Error as StdError;
let err: Box<dyn StdError> = Box::new(err);
let err = tor_error::Report(err).to_string();
assert!(
err.contains(exp),
"wrong message, got {:?}, exp {:?}",
err,
exp,
);
}
#[test]
fn bridges() {
// We make assumptions about the contents of `arti-example-config.toml` !
@ -798,7 +809,7 @@ mod test {
// Below, we annotate with `[1]` etc. where these assumptions are made.
// Filter examples that we don't want to test in this configuration
let filter_examples = |#[allow(unused_mut)] mut examples: ExampleSectionLines| {
let filter_examples = |#[allow(unused_mut)] mut examples: ExampleSectionLines| -> _ {
// [7], filter out the PTs
if cfg!(all(feature = "bridge-client", not(feature = "pt-client"))) {
let looks_like_addr =
@ -809,24 +820,32 @@ mod test {
examples
};
let resolve_examples = |examples: &ExampleSectionLines| -> TorClientConfig {
// Tests that one example parses, and returns what it parsed.
// If bridge support is completely disabled, checks that this configuration
// is rejected, as it should be, and returns a dummy value `((),)`
// (so that the rest of the test has something to "compare that we parsed it the same").
let resolve_examples = |examples: &ExampleSectionLines| {
// [7], check that the PT bridge is properly rejected
#[cfg(all(feature = "bridge-client", not(feature = "pt-client")))]
{
use std::error::Error as StdError;
let err = examples.resolve::<TorClientConfig>().unwrap_err();
let err: Box<dyn StdError> = Box::new(err);
let err = tor_error::Report(&err).to_string();
assert!(
err.contains("support disabled in cargo features"),
"wrong message, got {}",
err
);
expect_err_contains(err, "support disabled in cargo features");
}
let examples = filter_examples(examples.clone());
examples.resolve().unwrap()
#[cfg(feature = "bridge-client")]
{
examples.resolve::<TorClientConfig>().unwrap()
}
#[cfg(not(feature = "bridge-client"))]
{
let err = examples.resolve::<TorClientConfig>().unwrap_err();
expect_err_contains(err, "support disabled in cargo features");
// Use ((),) as the dummy unit value because () gives clippy conniptions
((),)
}
};
// [1], [2], narrow to just the nontrivial, non-default, examples

View File

@ -83,6 +83,7 @@ use tor_rtcompat::{BlockOn, Runtime};
use anyhow::{Context, Error, Result};
use clap::{value_parser, Arg, ArgAction, Command};
#[allow(unused_imports)]
use tracing::{error, info, warn};
/// Shorthand for a boxed and pinned Future.

View File

@ -31,7 +31,9 @@ libc = { version = "0.2", default-features = false }
derive_more = "0.99.3"
educe = "0.4.6"
futures-await-test = "0.3.0"
serde = { version = "1.0.103", features = ["derive"] }
tokio = { version = "1.7", features = ["macros", "rt", "rt-multi-thread", "time"] }
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

View File

@ -1 +1,2 @@
ADDED: Add extension trait providing BinaryHeap::retain on Stable (as retain_ext)
ADDED: Add derive_serde_raw! macro

View File

@ -47,6 +47,8 @@ pub mod n_key_set;
pub mod retry;
pub mod test_rng;
pub use paste::paste;
// ----------------------------------------------------------------------
/// Function with the signature of `Debug::fmt` that just prints `".."`
@ -234,3 +236,90 @@ macro_rules! macro_first_nonempty {
}
// ----------------------------------------------------------------------
/// Helper for defining a struct which can be (de)serialized several ways, including "natively"
///
/// Ideally we would have
/// ```rust ignore
/// #[derive(Deserialize)]
/// #[serde(try_from=Possibilities)]
/// struct Main { /* principal definition */ }
///
/// #[derive(Deserialize)]
/// #[serde(untagged)]
/// enum Possibilities { Main(Main), Other(OtherRepr) }
///
/// #[derive(Deserialize)]
/// struct OtherRepr { /* other representation we still want to read */ }
///
/// impl TryFrom<Possibilities> for Main { /* ... */ }
/// ```
///
/// But the impl for `Possibilities` ends up honouring the `try_from` on `Main`
/// so is recursive.
///
/// We solve that (ab)using serde's remote feature,
/// on a second copy of the struct definition.
///
/// See the Example for instructions.
/// It is important to **add test cases**
/// for all the representations you expect to parse and serialise,
/// since there are easy-to-write bugs,
/// for example omitting some of the necessary attributes.
///
/// # Generated output:
///
/// * The original struct definition, unmodified
/// * `#[derive(Serialize, Deserialize)] struct $main_Raw { }`
///
/// The `$main_Raw` struct ought not normally be to constructed anywhere,
/// and *isn't* convertible to or from the near-identical `$main` struct.
/// It exists only as a thing to feed to the serde remove derive,
/// and name in `with=`.
///
/// # Example
///
/// ```
/// use serde::{Deserialize, Serialize};
/// use tor_basic_utils::derive_serde_raw;
///
/// derive_serde_raw! {
/// #[derive(Deserialize, Serialize, Default, Clone, Debug)]
/// #[serde(try_from="BridgeConfigBuilderSerde", into="BridgeConfigBuilderSerde")]
/// pub struct BridgeConfigBuilder = "BridgeConfigBuilder" {
/// transport: Option<String>,
/// //...
/// }
/// }
///
/// #[derive(Serialize,Deserialize)]
/// #[serde(untagged)]
/// enum BridgeConfigBuilderSerde {
/// BridgeLine(String),
/// Dict(#[serde(with="BridgeConfigBuilder_Raw")] BridgeConfigBuilder),
/// }
///
/// impl TryFrom<BridgeConfigBuilderSerde> for BridgeConfigBuilder { //...
/// # type Error = std::io::Error;
/// # fn try_from(_: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> { todo!() } }
/// impl From<BridgeConfigBuilder> for BridgeConfigBuilderSerde { //...
/// # fn from(_: BridgeConfigBuilder) -> BridgeConfigBuilderSerde { todo!() } }
/// ```
#[macro_export]
macro_rules! derive_serde_raw { {
$( #[ $($attrs:meta)* ] )*
$vis:vis struct $main:ident=$main_s:literal
$($body:tt)*
} => {
$(#[ $($attrs)* ])*
$vis struct $main
$($body)*
$crate::paste! {
#[allow(non_camel_case_types)]
#[derive(Serialize, Deserialize)]
#[serde(remote=$main_s)]
struct [< $main _Raw >]
$($body)*
}
} }

View File

@ -13,7 +13,9 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = []
experimental = ["pt-client"]
experimental = []
full = ["pt-client"]
pt-client = ["tor-linkspec/pt-client"]
testing = []

View File

@ -16,18 +16,21 @@ Since a channel can be used for more than one circuit, it's
important to reuse channels when possible. This crate implements
a [`ChanMgr`] type that can be used to create channels on demand,
and return existing channels when they already exist.
## Compile-time features
* `pt-client` -- Build with APIs to support
pluggable transports.
### Experimental and unstable features
Note that the APIs enabled by these features are NOT covered by
semantic versioning[^1] guarantees: we might break them or remove
them between patch versions.
* `pt-client` -- Build with (as yet unimplemented) APIs to support
pluggable transports.
* `experimental` -- Build with all experimental features above.
(Currently, there are no experimental features in this crate,
but there may be some in the future.)
[^1]: Remember, semantic versioning is what makes various `cargo`
features work reliably. To be explicit: if you want `cargo update`

View File

@ -7,6 +7,7 @@ use async_trait::async_trait;
use tor_error::{HasKind, HasRetryTime};
use tor_linkspec::{OwnedChanTarget, PtTransportName};
use tor_proto::channel::Channel;
use tracing::debug;
/// An object that knows how to build `Channels` to `ChanTarget`s.
///
@ -55,6 +56,7 @@ where
type BuildSpec = OwnedChanTarget;
async fn build_channel(&self, target: &Self::BuildSpec) -> crate::Result<Self::Channel> {
debug!("Attempting to open a new channel to {target}");
self.connect_via_transport(target).await
}
}

View File

@ -13,13 +13,16 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = []
full = ["specific-relay"]
specific-relay = []
# Enable experimental APIs that are not yet officially supported.
#
# These APIs are not covered by semantic versioning. Using this
# feature voids your "semver warrantee".
experimental = ["experimental-api", "specific-relay"]
experimental = ["experimental-api"]
experimental-api = []
specific-relay = []
[dependencies]
async-trait = "0.1.2"

View File

@ -18,6 +18,12 @@ constructing a new one.
## Compile-time features
* `specific-relay`: Support for connecting to a relay via
specifically provided connection instructions, rather than
using information from a Tor network directory.
* `full`: Enable all features above.
### Experimental and unstable features
Note that the APIs enabled by these features are NOT covered by
@ -27,10 +33,6 @@ them between patch versions.
* `experimental-api`: Add additional non-stable APIs to our public
interfaces.
* `specific-relay`: Support for connecting to a relay via
specifically provided connection instructions, rather than
using information from a Tor network directory.
* `experimental`: Enable all the above experimental features.
[^1]: Remember, semantic versioning is what makes various `cargo`

View File

@ -45,7 +45,7 @@ use tor_proto::circuit::{CircParameters, ClientCirc, UniqId};
use tor_rtcompat::Runtime;
#[cfg(feature = "specific-relay")]
use tor_linkspec::OwnedChanTarget;
use tor_linkspec::IntoOwnedChanTarget;
use futures::task::SpawnExt;
use futures::StreamExt;
@ -411,12 +411,12 @@ impl<R: Runtime> CircMgr<R> {
/// This could be used, for example, to download a descriptor for a bridge.
#[cfg_attr(docsrs, doc(cfg(feature = "specific-relay")))]
#[cfg(feature = "specific-relay")]
pub async fn get_or_launch_dir_specific<T: Into<OwnedChanTarget>>(
pub async fn get_or_launch_dir_specific<T: IntoOwnedChanTarget>(
&self,
target: T,
) -> Result<ClientCirc> {
self.expire_circuits();
let usage = TargetCircUsage::DirSpecificTarget(target.into());
let usage = TargetCircUsage::DirSpecificTarget(target.to_owned());
self.mgr
.get_or_launch(&usage, DirInfo::Nothing)
.await

View File

@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize};
use std::fmt::{self, Display};
use std::sync::Arc;
use std::time::SystemTime;
use tor_linkspec::{HasChanMethod, HasRelayIds};
use tracing::debug;
use crate::path::{dirpath::DirPathBuilder, exitpath::ExitPathBuilder, TorPath};
@ -16,7 +15,7 @@ use tor_netdoc::types::policy::PortPolicy;
use tor_rtcompat::Runtime;
#[cfg(feature = "specific-relay")]
use tor_linkspec::OwnedChanTarget;
use tor_linkspec::{HasChanMethod, HasRelayIds, OwnedChanTarget};
use crate::isolation::{IsolationHelper, StreamIsolation};
use crate::mgr::{abstract_spec_find_supported, AbstractCirc, OpenEntry, RestrictionFailed};

View File

@ -37,6 +37,16 @@ pub enum ConfigBuildError {
/// The problem that makes them inconsistent
problem: String,
},
/// The requested configuration is not supported in this build
#[error("Field {field:?} specifies a configuration not supported in this build: {problem}")]
// TODO should we report the cargo feature, if applicable? And if so, of `arti`
// or of the underlying crate? This seems like a can of worms.
Unsupported {
/// The names of the (primary) field requesting the unsupported configuration
field: String,
/// The description of the problem
problem: String,
},
}
impl From<derive_builder::UninitializedFieldError> for ConfigBuildError {
@ -60,16 +70,21 @@ impl ConfigBuildError {
#[must_use]
pub fn within(&self, prefix: &str) -> Self {
use ConfigBuildError::*;
let addprefix = |field: &str| format!("{}.{}", prefix, field);
match self {
MissingField { field } => MissingField {
field: format!("{}.{}", prefix, field),
field: addprefix(field),
},
Invalid { field, problem } => Invalid {
field: format!("{}.{}", prefix, field),
field: addprefix(field),
problem: problem.clone(),
},
Inconsistent { fields, problem } => Inconsistent {
fields: fields.iter().map(|f| format!("{}.{}", prefix, f)).collect(),
fields: fields.iter().map(|f| addprefix(f)).collect(),
problem: problem.clone(),
},
Unsupported { field, problem } => Invalid {
field: addprefix(field),
problem: problem.clone(),
},
}

View File

@ -11,6 +11,9 @@
//! type. (Different lists with the same Rust type, but which ought to have a different
//! default, are different "kinds" and should each have a separately named type alias.)
//!
//! (Or, alternatively, with a hand-written builder type, make the builder field be
//! `Option<Vec<ElementBuilder>>`.)
//!
// An alternative design would be declare the field on `Outer` as `Vec<Thing>`, and to provide
// a `VecBuilder`. But:
//
@ -130,7 +133,6 @@
//! assert_eq!{ builder.build().unwrap().values, &[27, 12] }
//! ```
use std::fmt::Display;
use std::str::FromStr;
use educe::Educe;
@ -359,11 +361,15 @@ macro_rules! define_list_builder_accessors {
/// constructed and a mutable reference to the now-defaulted list of builders
/// will be returned.
$vis fn $things(&mut self) -> &mut Vec<$EntryBuilder> {
#[allow(unused_imports)]
use $crate::list_builder::DirectDefaultEmptyListBuilderAccessors as _;
self.$things.access()
}
/// Set the whole list (overriding the default)
$vis fn [<set_ $things>](&mut self, list: Vec<$EntryBuilder>) {
#[allow(unused_imports)]
use $crate::list_builder::DirectDefaultEmptyListBuilderAccessors as _;
*self.$things.access_opt_mut() = Some(list)
}
@ -371,6 +377,8 @@ macro_rules! define_list_builder_accessors {
///
/// If the list has not yet been set, or accessed, `&None` is returned.
$vis fn [<opt_ $things>](&self) -> &Option<Vec<$EntryBuilder>> {
#[allow(unused_imports)]
use $crate::list_builder::DirectDefaultEmptyListBuilderAccessors as _;
self.$things.access_opt()
}
@ -378,12 +386,66 @@ macro_rules! define_list_builder_accessors {
///
/// If the list has not yet been set, or accessed, `&mut None` is returned.
$vis fn [<opt_ $things _mut>](&mut self) -> &mut Option<Vec<$EntryBuilder>> {
#[allow(unused_imports)]
use $crate::list_builder::DirectDefaultEmptyListBuilderAccessors as _;
self.$things.access_opt_mut()
}
} )* }
}
}
/// Extension trait, an alternative to `define_list_builder_helper`
///
/// Useful for a handwritten `Builder` which wants to contain a list,
/// which is an `Option<Vec<ItemBuilder>>`.
///
/// # Example
///
/// ```
/// use tor_config::define_list_builder_accessors;
///
/// #[derive(Default)]
/// struct WombatBuilder {
/// leg_lengths: Option<Vec<u32>>,
/// }
///
/// define_list_builder_accessors! {
/// struct WombatBuilder {
/// leg_lengths: [u32],
/// }
/// }
///
/// let mut wb = WombatBuilder::default();
/// wb.leg_lengths().push(42);
///
/// assert_eq!(wb.leg_lengths, Some(vec![42]));
/// ```
///
/// It is not necessary to `use` this trait anywhere in your code;
/// the macro `define_list_builder_accessors` arranges to have it in scope where it needs it.
pub trait DirectDefaultEmptyListBuilderAccessors {
/// Entry type
type T;
/// Get access to the `Vec`, defaulting it
fn access(&mut self) -> &mut Vec<Self::T>;
/// Get access to the `Option<Vec>`
fn access_opt(&self) -> &Option<Vec<Self::T>>;
/// Get mutable access to the `Option<Vec>`
fn access_opt_mut(&mut self) -> &mut Option<Vec<Self::T>>;
}
impl<T> DirectDefaultEmptyListBuilderAccessors for Option<Vec<T>> {
type T = T;
fn access(&mut self) -> &mut Vec<T> {
self.get_or_insert_with(Vec::new)
}
fn access_opt(&self) -> &Option<Vec<T>> {
self
}
fn access_opt_mut(&mut self) -> &mut Option<Vec<T>> {
self
}
}
define_list_builder_helper! {
/// List of `T`, a straightforward type, being built as part of the configuration
///
@ -425,11 +487,13 @@ define_list_builder_helper! {
item_build: |item| Ok(item.clone());
}
/// Configuration item specifiable as a list of strings, or a single multi-line string
/// Configuration item specifiable as a list, or a single multi-line string
///
/// If a list of strings is supplied, they are each parsed with `FromStr`.
/// If a list is supplied, they are deserialized as builders.
/// If a single string is supplied, it is split into lines, and `#`-comments
/// and blank lines and whitespace are stripped, and then each line is parsed.
/// and blank lines and whitespace are stripped, and then each line is parsed
/// as a builder.
/// (Eventually, the builders will be built.)
///
/// For use with `sub_builder` and [`define_list_builder_helper`],
/// with `#[serde(try_from)]` and `#[serde(into)]`.
@ -471,8 +535,8 @@ define_list_builder_helper! {
/// built: LotteryNumberList = numbers;
/// default = generate_random();
/// item_build: |number| Ok(*number);
/// #[serde(try_from="MultilineListBuilder")]
/// #[serde(into="MultilineListBuilder")]
/// #[serde(try_from="MultilineListBuilder<u16>")]
/// #[serde(into="MultilineListBuilder<u16>")]
/// }
///
/// convert_helper_via_multi_line_list_builder! {
@ -487,7 +551,7 @@ define_list_builder_helper! {
/// }
/// }
///
/// let lc: LotteryConfigBuilder = toml::from_str(r#"winners = ["1","2","3"]"#).unwrap();
/// let lc: LotteryConfigBuilder = toml::from_str(r#"winners = [1,2,3]"#).unwrap();
/// let lc = lc.build().unwrap();
/// assert_eq!{ lc.winners, [1,2,3] }
///
@ -508,7 +572,7 @@ define_list_builder_helper! {
#[serde(untagged)]
#[educe(Default)]
#[non_exhaustive]
pub enum MultilineListBuilder {
pub enum MultilineListBuilder<EB> {
/// Config key not present
#[educe(Default)]
Unspecified,
@ -516,8 +580,8 @@ pub enum MultilineListBuilder {
/// Config key was a string which is to be parsed line-by-line
String(String),
/// Config key was a list of the strings to be parsed
List(Vec<String>),
/// Config key was a list of the individual entry builders
List(Vec<EB>),
}
/// Error from trying to parse a MultilineListBuilder as a list of particular items
@ -542,26 +606,23 @@ pub struct MultilineListBuilderError<E: std::error::Error + Clone + Send + Sync>
error: E,
}
impl<I> From<Option<Vec<I>>> for MultilineListBuilder
where
I: Display,
{
fn from(list: Option<Vec<I>>) -> Self {
impl<EB> From<Option<Vec<EB>>> for MultilineListBuilder<EB> {
fn from(list: Option<Vec<EB>>) -> Self {
use MultilineListBuilder as MlLB;
match list {
None => MlLB::Unspecified,
Some(list) => MlLB::List(list.into_iter().map(|i| i.to_string()).collect()),
Some(list) => MlLB::List(list),
}
}
}
impl<I> TryInto<Option<Vec<I>>> for MultilineListBuilder
impl<EB> TryInto<Option<Vec<EB>>> for MultilineListBuilder<EB>
where
I: FromStr,
I::Err: std::error::Error + Clone + Send + Sync,
EB: FromStr,
EB::Err: std::error::Error + Clone + Send + Sync,
{
type Error = MultilineListBuilderError<I::Err>;
fn try_into(self) -> Result<Option<Vec<I>>, Self::Error> {
type Error = MultilineListBuilderError<EB::Err>;
fn try_into(self) -> Result<Option<Vec<EB>>, Self::Error> {
use MultilineListBuilder as MlLB;
/// Helper for parsing each line of `iter` and collecting the results
@ -586,7 +647,7 @@ where
Ok(match self {
MlLB::Unspecified => None,
MlLB::List(list) => parse_collect(list.iter().map(|s| s.as_ref()).enumerate())?,
MlLB::List(list) => Some(list),
MlLB::String(s) => parse_collect(
s.lines()
.enumerate()
@ -608,17 +669,17 @@ where
macro_rules! convert_helper_via_multi_line_list_builder { {
struct $ListBuilder:ident { $things:ident: [$EntryBuilder:ty] $(,)? }
} => {
impl std::convert::TryFrom<$crate::MultilineListBuilder> for $ListBuilder {
impl std::convert::TryFrom<$crate::MultilineListBuilder<$EntryBuilder>> for $ListBuilder {
type Error = $crate::MultilineListBuilderError<<$EntryBuilder as std::str::FromStr>::Err>;
fn try_from(mllb: $crate::MultilineListBuilder)
fn try_from(mllb: $crate::MultilineListBuilder<$EntryBuilder>)
-> std::result::Result<$ListBuilder, Self::Error> {
Ok($ListBuilder { $things: mllb.try_into()? })
}
}
impl From<$ListBuilder> for MultilineListBuilder {
fn from(lb: $ListBuilder) -> MultilineListBuilder {
impl From<$ListBuilder> for MultilineListBuilder<$EntryBuilder> {
fn from(lb: $ListBuilder) -> MultilineListBuilder<$EntryBuilder> {
lb.$things.into()
}
}

View File

@ -482,7 +482,7 @@ world = \"nonsense\"
let xd = td.path().join("nonexistent.d/");
std::fs::create_dir(&d).unwrap();
std::fs::write(&cf, EX_TOML).unwrap();
std::fs::write(&df, EX2_TOML).unwrap();
std::fs::write(df, EX2_TOML).unwrap();
std::fs::write(d.join("not-toml"), "SYNTAX ERROR").unwrap();
let files = vec![

View File

@ -486,6 +486,7 @@ impl Requestable for RoutersOwnDescRequest {
/// List the encodings we accept
fn encodings() -> String {
#[allow(unused_mut)]
let mut encodings = "deflate, identity".to_string();
#[cfg(feature = "xz")]
{

View File

@ -13,8 +13,8 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = ["mmap", "compression"]
full = ["routerdesc"]
experimental = ["experimental-api", "dirfilter", "bridge-client"]
full = ["routerdesc", "bridge-client", "default"]
experimental = ["experimental-api", "dirfilter"]
bridge-client = ["tor-circmgr/specific-relay", "tor-guardmgr/bridge-client", "routerdesc"]
mmap = ["memmap2"]

View File

@ -18,16 +18,23 @@ keeping a cache of it on disk.
## Compile-time features
`mmap` (default) -- Use memory mapping to reduce the memory load for
reading large directory objects from disk.
* `mmap` (default) -- Use memory mapping to reduce the memory load for
reading large directory objects from disk.
`static` -- Try to link with a static copy of sqlite3.
* `routerdesc` -- (Incomplete) support for downloading and storing
router descriptors.
`routerdesc` -- (Incomplete) support for downloading and storing
router descriptors.
* `compression` (default) -- Build support for downloading compressed
documents. Requires a C compiler.
`compression` (default) -- Build support for downloading compressed
documents. Requires a C compiler.
* `bridge-client`: Provide APIs used to fetch
and use bridge information.
* `full` -- Enable all features above.
### Non-additive features
* `static` -- Try to link with a static copy of sqlite3.
### Experimental and unstable features
@ -41,9 +48,6 @@ them between patch versions.
* `dirfilter`: enable an experimental mechanism to modify incoming
directory information before it is used.
* `bridge-client`: Provide (as yet unimplented) APIs used to fetch
and use bridge information.
* `experimental`: Enable all the above experimental features.
[^1]: Remember, semantic versioning is what makes various `cargo`

View File

@ -149,7 +149,7 @@ fn setup() -> (TempDir, Bdm, R, M, BridgeKey, rusqlite::Connection) {
let store = Arc::new(Mutex::new(Box::new(store) as _));
let sql_path = db_tmp_dir.path().join("db.sql");
let conn = rusqlite::Connection::open(&sql_path).unwrap();
let conn = rusqlite::Connection::open(sql_path).unwrap();
let bdm = BridgeDescMgr::<R, M>::new_internal(
runtime.clone(),

View File

@ -109,7 +109,8 @@ use strum;
/// A Result as returned by this crate.
pub type Result<T> = std::result::Result<T, Error>;
/// Storage manager used by [`DirMgr`] and [`BridgeDescMgr`]
/// Storage manager used by [`DirMgr`] and
/// [`BridgeDescMgr`](bridgedesc::BridgeDescMgr)
///
/// Internally, this wraps up a sqlite database.
///

View File

@ -404,7 +404,7 @@ impl Store for SqliteStore {
if let Some(row) = rows.next()? {
let meta = cmeta_from_row(row)?;
let fname: String = row.get(5)?;
let text = self.read_blob(&fname)?;
let text = self.read_blob(fname)?;
Ok(Some((text, meta)))
} else {
Ok(None)
@ -978,7 +978,7 @@ pub(crate) mod test {
pub(crate) fn new_empty() -> Result<(TempDir, SqliteStore)> {
let tmp_dir = tempdir().unwrap();
let sql_path = tmp_dir.path().join("db.sql");
let conn = rusqlite::Connection::open(&sql_path)?;
let conn = rusqlite::Connection::open(sql_path)?;
let blob_dir = fs_mistrust::Mistrust::builder()
.dangerously_trust_everyone()
.build()

View File

@ -13,8 +13,8 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = []
experimental = ["bridge-client"]
full = ["bridge-client", "pt-client"]
experimental = []
# Support for using bridges as a client. Note that this is not the same as
# the pt-client feature, since here we are not concerned with
@ -42,6 +42,7 @@ pin-project = "1"
postage = { version = "0.5.0", default-features = false, features = ["futures-traits"] }
rand = "0.8"
retain_mut = "0.1.3"
safelog = { path = "../safelog", version = "0.1.2" }
serde = { version = "1.0.103", features = ["derive"] }
strum = { version = "0.24", features = ["derive"] }
thiserror = "1"
@ -61,10 +62,13 @@ tracing = "0.1.18"
[dev-dependencies]
float_eq = "1.0.0"
serde_json = "1.0.50"
toml = "0.5.6"
tor-netdir = { path = "../tor-netdir", version = "0.6.0", features = ["testing"] }
tor-netdoc = { path = "../tor-netdoc", version = "0.5.2" }
tor-persist = { path = "../tor-persist", version = "0.5.1", features = ["testing"] }
tor-rtcompat = { path = "../tor-rtcompat", version = "0.7.0", features = ["tokio", "native-tls"] }
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

View File

@ -93,4 +93,16 @@ Tor's current guard selection algorithm is described in Tor's
[`guard-spec.txt`](https://gitlab.torproject.org/tpo/core/torspec/-/raw/main/guard-spec.txt)
document.
## Compile-time features
* `bridge-client`: Build with support for bridges. (Bridges are relays
that are not listed in the Tor network directory, which can be
used for anti-censorship purposes.)
* `pt-client`: Build with support for guards that can be contacted
using pluggable transports. (A pluggable transport is an alternative
mechanism for contacting a Tor relay, for censorship avoidance.)
* `full`: Enable all features above.
License: MIT OR Apache-2.0

View File

@ -11,7 +11,7 @@ mod config;
mod descs;
mod relay;
pub use config::{BridgeConfig, BridgeParseError};
pub use config::{BridgeConfig, BridgeConfigBuilder, BridgeParseError};
pub use descs::{BridgeDesc, BridgeDescError, BridgeDescEvent, BridgeDescList, BridgeDescProvider};
pub use relay::BridgeRelay;

View File

@ -1,18 +1,29 @@
//! Configuration logic and types for bridges.
use std::fmt::{self, Display};
use std::iter;
use std::net::SocketAddr;
use std::str::FromStr;
use thiserror::Error;
use itertools::{chain, Itertools};
use serde::{Deserialize, Serialize};
use tor_basic_utils::derive_serde_raw;
use tor_config::define_list_builder_accessors;
use tor_config::{impl_standard_builder, ConfigBuildError};
use tor_linkspec::RelayId;
use tor_linkspec::TransportId;
use tor_linkspec::{ChanTarget, ChannelMethod, HasChanMethod};
use tor_linkspec::{HasAddrs, HasRelayIds, RelayIdRef, RelayIdType};
use tor_linkspec::{RelayId, RelayIdError, TransportIdError};
use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
use tor_linkspec::BridgeAddr;
#[cfg(feature = "pt-client")]
use tor_linkspec::{PtAddrError, PtTarget, PtTargetAddr, PtTargetInvalidSetting};
use tor_linkspec::PtTarget;
mod err;
pub use err::BridgeParseError;
/// A relay not listed on the main tor network, used for anticensorship.
///
@ -68,10 +79,6 @@ pub struct BridgeConfig {
/// The Ed25519 identity of the bridge.
ed_id: Option<Ed25519Identity>,
}
// TODO pt-client: when implementing deserialization for this type, make sure
// that it can accommodate a large variety of possible configurations methods,
// and check that the toml looks okay. For discussion see
// https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/704/diffs#note_2835271
impl HasRelayIds for BridgeConfig {
fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
@ -97,104 +104,282 @@ impl HasAddrs for BridgeConfig {
impl ChanTarget for BridgeConfig {}
/// Error when parsing a bridge line from a string
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
pub enum BridgeParseError {
/// Bridge line was empty
#[error("Bridge line was empty")]
Empty,
derive_serde_raw! {
/// Builder for a `BridgeConfig`.
///
/// Construct this with [`BridgeConfigBuilder::default()`] or [`BridgeConfig::builder()`],
/// call setter methods, and then call `build().`
//
// `BridgeConfig` contains a `ChannelMethod`. This is convenient for its users,
// but means we can't use `#[derive(Builder)]` to autogenerate this.
#[derive(Deserialize, Serialize, Default, Clone, Debug)]
#[serde(try_from="BridgeConfigBuilderSerde", into="BridgeConfigBuilderSerde")]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct BridgeConfigBuilder = "BridgeConfigBuilder" {
/// The `PtTransportName`, but not yet parsed or checked.
///
/// `""` and `"-"` and `"bridge"` all mean "do not use a pluggable transport".
transport: Option<String>,
/// Expected PT name or host:port, looked a bit like a PT name, but didn't parse
#[error(
"Cannot parse {word:?} as PT name ({pt_error}), nor as direct bridge IpAddress:ORPort"
)]
InvalidPtOrAddr {
/// The offending word
word: String,
/// Why we couldn't parse it as a PT name
pt_error: TransportIdError,
},
/// Host:ORPort
///
/// When using a pluggable transport, only one address is allowed.
addrs: Option<Vec<BridgeAddr>>,
/// Expected PT name or host:port, looked a bit like a host:port, but didn't parse
#[error(
"Cannot parse {word:?} as direct bridge IpAddress:ORPort ({addr_error}), nor as PT name"
)]
InvalidIpAddrOrPt {
/// The offending word
word: String,
/// Why we couldn't parse it as an IP address and port
addr_error: std::net::AddrParseError,
},
/// IDs
///
/// No more than one ID of each type is permitted.
ids: Option<Vec<RelayId>>,
/// Cannot parse pluggable transport host address
#[cfg(feature = "pt-client")]
#[error("Cannot parse {word:?} as pluggable transport Host:ORPort")]
InvalidIPtHostAddr {
/// The offending word
word: String,
/// Why we couldn't parse it as a PT target Host:ORPort
#[source]
source: PtAddrError,
},
/// Settings (for the transport)
settings: Option<Vec<(String, String)>>,
}
}
impl_standard_builder! { BridgeConfig: !Default }
/// Cannot parse value as identity key, or PT key=value
#[error("Cannot parse {word:?} as identity key ({id_error}), or PT key=value")]
InvalidIdentityOrParameter {
/// The offending word
word: String,
/// Why we couldn't parse it as a fingerprint
id_error: RelayIdError,
},
/// serde representation of a `BridgeConfigBuilder`
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum BridgeConfigBuilderSerde {
/// We understand a bridge line
BridgeLine(String),
/// We understand a dictionary matching BridgeConfigBuilder
Dict(#[serde(with = "BridgeConfigBuilder_Raw")] BridgeConfigBuilder),
}
/// PT key=value parameter does not contain an equals sign
#[cfg(feature = "pt-client")]
#[error("Expected PT key=value parameter, found {word:?} (which lacks an equals sign)")]
InvalidPtKeyValue {
/// The offending word
word: String,
},
impl TryFrom<BridgeConfigBuilderSerde> for BridgeConfigBuilder {
type Error = BridgeParseError;
fn try_from(input: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> {
use BridgeConfigBuilderSerde::*;
match input {
BridgeLine(s) => s.parse(),
Dict(d) => Ok(d),
}
}
}
/// Invalid pluggable transport setting syntax
#[cfg(feature = "pt-client")]
#[error("Cannot parse {word:?} as a PT key=value parameter")]
InvalidPluggableTransportSetting {
/// The offending word
word: String,
/// Why we couldn't parse it
#[source]
source: PtTargetInvalidSetting,
},
impl From<BridgeConfigBuilder> for BridgeConfigBuilderSerde {
fn from(input: BridgeConfigBuilder) -> BridgeConfigBuilderSerde {
use BridgeConfigBuilderSerde::*;
// Try to serialize as a bridge line if we can
match input.build() {
Ok(bridge) => BridgeLine(bridge.to_string()),
Err(_) => Dict(input),
}
}
}
/// More than one identity of the same type specified
#[error("More than one identity of the same type specified, at {word:?}")]
MultipleIdentitiesOfSameType {
/// The offending word
word: String,
},
impl BridgeConfigBuilder {
/// Set the transport protocol name (eg, a pluggable transport) to use.
///
/// The empty string `""`, a single hyphen `"-"`, and the word `"bridge"`,
/// all mean to connect directly;
/// i.e., passing one of this is equivalent to
/// calling [`direct()`](BridgeConfigBuilder::direct).
///
/// The value is not checked at this point.
pub fn transport(&mut self, transport: impl Into<String>) -> &mut Self {
self.transport = Some(transport.into());
self
}
/// Identity specified of unsupported type
#[error("Identity specified but not of supported type, at {word:?}")]
UnsupportedIdentityType {
/// The offending word
word: String,
},
/// Specify to use a direct connection.
pub fn direct(&mut self) -> &mut Self {
self.transport("")
}
/// Parameters may only be specified with a pluggable transport
#[error("Parameters supplied but not valid without a pluggable transport")]
DirectParametersNotAllowed,
/// Add a pluggable transport setting
pub fn push_setting(&mut self, k: impl Into<String>, v: impl Into<String>) -> &mut Self {
self.settings().push((k.into(), v.into()));
self
}
}
/// Every bridge must have an RSA identity
#[error("Bridge line lacks specification of RSA identity key")]
NoRsaIdentity,
impl BridgeConfigBuilder {
/// Build a `BridgeConfig`
pub fn build(&self) -> Result<BridgeConfig, ConfigBuildError> {
let transport = self.transport.as_deref().unwrap_or_default();
let addrs = self.addrs.as_deref().unwrap_or_default();
let settings = self.settings.as_deref().unwrap_or_default();
/// Pluggable transport support disabled in cargo features
// We deliberately make this one *not* configured out if PT support is enabled
#[error("Pluggable transport requested ({word:?} is not an IpAddress:ORPort), but support disabled in cargo features")]
PluggableTransportsNotSupported {
/// The offending word
word: String,
},
// Error construction helpers
let inconsist_transp = |field: &str, problem: &str| ConfigBuildError::Inconsistent {
fields: vec![field.into(), "transport".into()],
problem: problem.into(),
};
let unsupported = |field: String, problem: &dyn Display| ConfigBuildError::Unsupported {
field,
problem: problem.to_string(),
};
#[cfg_attr(not(feature = "pt-client"), allow(unused_variables))]
let invalid = |field: String, problem: &dyn Display| ConfigBuildError::Invalid {
field,
problem: problem.to_string(),
};
let transp: TransportId = transport
.parse()
.map_err(|e| invalid("transport".into(), &e))?;
// This match seems redundant, but it allows us to apply #[cfg] to the branches,
// which isn't possible with `if ... else ...`.
let addrs = match () {
() if transp.is_builtin() => {
if !settings.is_empty() {
return Err(inconsist_transp(
"settings",
"Specified `settings` for a direct bridge connection",
));
}
let addrs = addrs.iter().filter_map(|pta| match pta {
BridgeAddr::IpPort(sa) => Some(Ok(*sa)),
BridgeAddr::HostPort(..) => Some(Err(
"`addrs` contains hostname and port, but only numeric addresses are supported for a direct bridge connection",
)),
BridgeAddr::None => None,
_ => Some(Err(
"`addrs` contains unspported target address type, but only numeric addresses are supported for a direct bridge connection"
)),
}).collect::<Result<Vec<SocketAddr>,&str>>().map_err(|problem| inconsist_transp(
"addrs",
problem,
))?;
if addrs.is_empty() {
return Err(inconsist_transp(
"addrs",
"Missing `addrs` for a direct bridge connection",
));
}
ChannelMethod::Direct(addrs)
}
#[cfg(feature = "pt-client")]
() if transp.as_pluggable().is_some() => {
let transport = transp.into_pluggable().expect("became not pluggable!");
let addr =
match addrs {
[] => BridgeAddr::None,
[addr] => addr.clone(),
[_, _, ..] => return Err(inconsist_transp(
"addrs",
"Transport (non-direct bridge) only supports a single nominal address",
)),
};
let mut target = PtTarget::new(transport, addr);
for (i, (k, v)) in settings.iter().enumerate() {
// Using PtTargetSettings TryFrom would prevent us reporting the index i
target
.push_setting(k, v)
.map_err(|e| invalid(format!("settings.{}", i), &e))?;
}
ChannelMethod::Pluggable(target)
}
() => {
// With current code, this can only happen if tor-linkspec has pluggable
// transports enabled, but we don't. But if `TransportId` gains other
// inner variants, it would trigger.
return Err(unsupported(
"transport".into(),
&format_args!("support for selected transport '{}' disabled in tor-guardmgr cargo features",
transp),
));
}
};
let mut rsa_id = None;
let mut ed_id = None;
/// Helper to store an id in `rsa_id` or `ed_id`
fn store_id<T: Clone>(
u: &mut Option<T>,
desc: &str,
v: &T,
) -> Result<(), ConfigBuildError> {
if u.is_some() {
Err(ConfigBuildError::Invalid {
field: "ids".into(),
problem: format!("multiple different ids of the same type ({})", desc),
})
} else {
*u = Some(v.clone());
Ok(())
}
}
for (i, id) in self.ids.as_deref().unwrap_or_default().iter().enumerate() {
match id {
RelayId::Rsa(rsa) => store_id(&mut rsa_id, "RSA", rsa)?,
RelayId::Ed25519(ed) => store_id(&mut ed_id, "ed25519", ed)?,
other => {
return Err(unsupported(
format!("ids.{}", i),
&format_args!("unsupported bridge id type {}", other.id_type()),
))
}
}
}
let rsa_id = rsa_id.ok_or_else(|| ConfigBuildError::Invalid {
field: "ids".into(),
problem: "need an RSA identity".into(),
})?;
Ok(BridgeConfig {
addrs,
rsa_id,
ed_id,
})
}
}
/// `BridgeConfigBuilder` parses the same way as `BridgeConfig`
//
// We implement it this way round (rather than having the `impl FromStr for BridgeConfig`
// call this and then `build`, because the `BridgeConfig` parser
// does a lot of bespoke checking of the syntax and semantics.
// Doing it the other way, we'd have to unwrap a supposedly-never-existing `ConfigBuildError`,
// in `BridgeConfig`'s `FromStr` impl.
impl FromStr for BridgeConfigBuilder {
type Err = BridgeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bridge: BridgeConfig = s.parse()?;
let (transport, addrs, settings) = match bridge.addrs {
ChannelMethod::Direct(addrs) => (
"".into(),
addrs.into_iter().map(BridgeAddr::IpPort).collect(),
vec![],
),
#[cfg(feature = "pt-client")]
ChannelMethod::Pluggable(target) => {
let (transport, addr, settings) = target.into_parts();
(transport.into_inner(), vec![addr], settings.into_inner())
}
};
let ids = chain!(
iter::once(bridge.rsa_id.into()),
bridge.ed_id.into_iter().map(Into::into),
)
.collect_vec();
Ok(BridgeConfigBuilder {
transport: Some(transport),
addrs: Some(addrs),
settings: Some(settings),
ids: Some(ids),
})
}
}
define_list_builder_accessors! {
struct BridgeConfigBuilder {
pub addrs: [BridgeAddr],
pub ids: [RelayId],
pub settings: [(String,String)],
}
}
impl FromStr for BridgeConfig {
@ -248,7 +433,7 @@ impl FromStr for BridgeConfig {
word: word.to_string(),
source,
})?
.unwrap_or(PtTargetAddr::None);
.unwrap_or(BridgeAddr::None);
ChannelMethod::Pluggable(PtTarget::new(pt_name, addr))
}
}
@ -391,7 +576,7 @@ mod test {
use super::*;
#[cfg(feature = "pt-client")]
fn mk_pt_target(name: &str, addr: PtTargetAddr, params: &[(&str, &str)]) -> ChannelMethod {
fn mk_pt_target(name: &str, addr: BridgeAddr, params: &[(&str, &str)]) -> ChannelMethod {
let mut target = PtTarget::new(name.parse().unwrap(), addr);
for &(k, v) in params {
target.push_setting(k, v).unwrap();
@ -453,7 +638,7 @@ mod test {
], BridgeConfig {
addrs: mk_pt_target(
"obfs4",
PtTargetAddr::IpPort("38.229.33.83:80".parse().unwrap()),
BridgeAddr::IpPort("38.229.33.83:80".parse().unwrap()),
&[
("cert", "VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op" ),
("iat-mode", "1"),
@ -470,7 +655,7 @@ mod test {
], BridgeConfig {
addrs: mk_pt_target(
"obfs4",
PtTargetAddr::HostPort("some-host".into(), 80),
BridgeAddr::HostPort("some-host".into(), 80),
&[
("iat-mode", "1"),
],
@ -574,4 +759,189 @@ mod test {
"More than one identity of the same type specified",
);
}
#[test]
fn config_api() {
let chk_bridgeline = |line: &str, jsons: &[&str], f: &dyn Fn(&mut BridgeConfigBuilder)| {
eprintln!(" ---- chk_bridgeline ----\n{}", line);
let mut bcb = BridgeConfigBuilder::default();
f(&mut bcb);
let built = bcb.build().unwrap();
assert_eq!(&built, &line.parse::<BridgeConfig>().unwrap());
let parsed_b: BridgeConfigBuilder = line.parse().unwrap();
assert_eq!(&built, &parsed_b.build().unwrap());
let re_serialized = serde_json::to_value(&bcb).unwrap();
assert_eq!(re_serialized, serde_json::Value::String(line.to_string()));
for json in jsons {
let from_dict: BridgeConfigBuilder = serde_json::from_str(json).unwrap();
assert_eq!(&from_dict, &bcb);
assert_eq!(&built, &from_dict.build().unwrap());
}
};
chk_bridgeline(
"38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
&[r#"{
"addrs": ["38.229.33.83:80"],
"ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
"$0bac39417268b96b9f514e7f63fa6fba1a788955"]
}"#],
&|bcb| {
bcb.addrs().push("38.229.33.83:80".parse().unwrap());
bcb.ids().push("ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE".parse().unwrap());
bcb.ids().push("$0bac39417268b96b9f514e7f63fa6fba1a788955".parse().unwrap());
}
);
#[cfg(feature = "pt-client")]
chk_bridgeline(
"obfs4 some-host:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 iat-mode=1",
&[r#"{
"transport": "obfs4",
"addrs": ["some-host:80"],
"ids": ["$0bac39417268b96b9f514e7f63fa6fba1a788955"],
"settings": [["iat-mode", "1"]]
}"#],
&|bcb| {
bcb.transport("obfs4");
bcb.addrs().push("some-host:80".parse().unwrap());
bcb.ids()
.push("$0bac39417268b96b9f514e7f63fa6fba1a788955".parse().unwrap());
bcb.push_setting("iat-mode", "1");
},
);
let chk_broken = |emsg: &str, jsons: &[&str], f: &dyn Fn(&mut BridgeConfigBuilder)| {
eprintln!(" ---- chk_bridgeline ----\n{:?}", emsg);
let mut bcb = BridgeConfigBuilder::default();
f(&mut bcb);
for json in jsons {
let from_dict: BridgeConfigBuilder = serde_json::from_str(json).unwrap();
assert_eq!(&from_dict, &bcb);
}
let err = bcb.build().expect_err("succeeded?!");
let got_emsg = err.to_string();
assert!(
got_emsg.contains(emsg),
"wrong error message: got_emsg={:?} err={:?} expected={:?}",
&got_emsg,
&err,
emsg,
);
// This is a kludge. When we serialize `Option<Vec<_>>` as JSON,
// we get a `Null` entry. These `Null`s aren't in our test cases and we don't
// really want them, although it's OK that they're there in the JSON.
// The TOML serialization omits them completely, though.
// So, we serialize the builder as TOML, and then convert the TOML to JSON Value.
// That launders out the `Null`s and gives us the same Value as our original JSON.
let toml_got = toml::to_string(&bcb).unwrap();
let json_got: serde_json::Value = toml::from_str(&toml_got).unwrap();
let json_exp: serde_json::Value = serde_json::from_str(jsons[0]).unwrap();
assert_eq!(&json_got, &json_exp);
};
chk_broken(
"Specified `settings` for a direct bridge connection",
&[r#"{
"settings": [["hi","there"]]
}"#],
&|bcb| {
bcb.settings().push(("hi".into(), "there".into()));
},
);
#[cfg(not(feature = "pt-client"))]
chk_broken(
"Not compiled with pluggable transport support",
&[r#"{
"transport": "obfs4"
}"#],
&|bcb| {
bcb.transport("obfs4");
},
);
#[cfg(feature = "pt-client")]
chk_broken(
"only numeric addresses are supported for a direct bridge connection",
&[r#"{
"transport": "bridge",
"addrs": ["some-host:80"]
}"#],
&|bcb| {
bcb.transport("bridge");
bcb.addrs().push("some-host:80".parse().unwrap());
},
);
chk_broken(
"Missing `addrs` for a direct bridge connection",
&[r#"{
"transport": "-"
}"#],
&|bcb| {
bcb.transport("-");
},
);
#[cfg(feature = "pt-client")]
chk_broken(
"only supports a single nominal address",
&[r#"{
"transport": "obfs4",
"addrs": ["some-host:80", "38.229.33.83:80"]
}"#],
&|bcb| {
bcb.transport("obfs4");
bcb.addrs().push("some-host:80".parse().unwrap());
bcb.addrs().push("38.229.33.83:80".parse().unwrap());
},
);
chk_broken(
"multiple different ids of the same type (ed25519)",
&[r#"{
"addrs": ["38.229.33.83:80"],
"ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
"ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISA"]
}"#],
&|bcb| {
bcb.addrs().push("38.229.33.83:80".parse().unwrap());
bcb.ids().push(
"ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
.parse()
.unwrap(),
);
bcb.ids().push(
"ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISA"
.parse()
.unwrap(),
);
},
);
chk_broken(
"need an RSA identity",
&[r#"{
"addrs": ["38.229.33.83:80"],
"ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"]
}"#],
&|bcb| {
bcb.addrs().push("38.229.33.83:80".parse().unwrap());
bcb.ids().push(
"ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
.parse()
.unwrap(),
);
},
);
}
}

View File

@ -0,0 +1,120 @@
//! Error when parsing a bridge line from a string
//
// This module is included even if we don't have bridge support enabled,
// but all but one of the error variants are suppressed, making the error a unit enum.
use thiserror::Error;
/// Error when parsing a bridge line from a string
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
pub enum BridgeParseError {
/// Bridge line was empty
#[cfg(feature = "bridge-client")]
#[error("Bridge line was empty")]
Empty,
/// Expected PT name or host:port, looked a bit like a PT name, but didn't parse
#[cfg(feature = "bridge-client")]
#[error(
"Cannot parse {word:?} as PT name ({pt_error}), nor as direct bridge IpAddress:ORPort"
)]
InvalidPtOrAddr {
/// The offending word
word: String,
/// Why we couldn't parse it as a PT name
pt_error: tor_linkspec::TransportIdError,
},
/// Expected PT name or host:port, looked a bit like a host:port, but didn't parse
#[cfg(feature = "bridge-client")]
#[error(
"Cannot parse {word:?} as direct bridge IpAddress:ORPort ({addr_error}), nor as PT name"
)]
InvalidIpAddrOrPt {
/// The offending word
word: String,
/// Why we couldn't parse it as an IP address and port
addr_error: std::net::AddrParseError,
},
/// Cannot parse pluggable transport host address
#[cfg(feature = "pt-client")]
#[error("Cannot parse {word:?} as pluggable transport Host:ORPort")]
InvalidIPtHostAddr {
/// The offending word
word: String,
/// Why we couldn't parse it as a PT target Host:ORPort
#[source]
source: tor_linkspec::BridgeAddrError,
},
/// Cannot parse value as identity key, or PT key=value
#[cfg(feature = "bridge-client")]
#[error("Cannot parse {word:?} as identity key ({id_error}), or PT key=value")]
InvalidIdentityOrParameter {
/// The offending word
word: String,
/// Why we couldn't parse it as a fingerprint
id_error: tor_linkspec::RelayIdError,
},
/// PT key=value parameter does not contain an equals sign
#[cfg(feature = "pt-client")]
#[error("Expected PT key=value parameter, found {word:?} (which lacks an equals sign)")]
InvalidPtKeyValue {
/// The offending word
word: String,
},
/// Invalid pluggable transport setting syntax
#[cfg(feature = "pt-client")]
#[error("Cannot parse {word:?} as a PT key=value parameter")]
InvalidPluggableTransportSetting {
/// The offending word
word: String,
/// Why we couldn't parse it
#[source]
source: tor_linkspec::PtTargetInvalidSetting,
},
/// More than one identity of the same type specified
#[cfg(feature = "bridge-client")]
#[error("More than one identity of the same type specified, at {word:?}")]
MultipleIdentitiesOfSameType {
/// The offending word
word: String,
},
/// Identity specified of unsupported type
#[cfg(feature = "bridge-client")]
#[error("Identity specified but not of supported type, at {word:?}")]
UnsupportedIdentityType {
/// The offending word
word: String,
},
/// Parameters may only be specified with a pluggable transport
#[cfg(feature = "bridge-client")]
#[error("Parameters supplied but not valid without a pluggable transport")]
DirectParametersNotAllowed,
/// Every bridge must have an RSA identity
#[cfg(feature = "bridge-client")]
#[error("Bridge line lacks specification of RSA identity key")]
NoRsaIdentity,
/// Pluggable transport support disabled in cargo features
// We deliberately make this one *not* configured out if PT support is enabled
#[cfg(feature = "bridge-client")]
#[error("Pluggable transport requested ({word:?} is not an IpAddress:ORPort), but support disabled in cargo features")]
PluggableTransportsNotSupported {
/// The offending word
word: String,
},
/// Bridge support disabled in cargo features
// We deliberately make this one *not* configured out if bridge support is enabled
#[error("Bridge requested, but support disabled in cargo features")]
BridgesNotSupported,
}

View File

@ -0,0 +1,60 @@
//! Bridges (stub module, bridges disabled in cargo features)
use serde::{Deserialize, Deserializer, Serialize};
use std::str::FromStr;
use tor_config::ConfigBuildError;
#[path = "bridge/config/err.rs"]
mod err;
pub use err::BridgeParseError;
/// Configuration for a bridge - uninhabited placeholder type
///
/// This type appears in configuration APIs as a stand-in,
/// when the `bridge-client` cargo feature is not enabled.
///
/// The type is uninhabited: without this feature, you cannot create a `BridgeConfig`.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum BridgeConfig {}
/// Configuration builder for a bridge - dummy type
///
/// This type appears in configuration APIs as a stand-in,
/// when the `bridge-client` cargo feature is not enabled.
///
/// It can be deserialized, but you cannot actually build a `BridgeConfig` from it.
//
// Making this type inhabited significantly improves the error messages
// when bridges are requested when support isn't enabled.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[non_exhaustive]
#[derive(Serialize)]
pub struct BridgeConfigBuilder {}
impl<'de> Deserialize<'de> for BridgeConfigBuilder {
fn deserialize<D>(_: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(BridgeConfigBuilder {})
}
}
impl BridgeConfigBuilder {
/// Build (dummy function, cannot ever be called)
pub fn build(&self) -> Result<BridgeConfig, ConfigBuildError> {
Err(ConfigBuildError::Invalid {
field: "(bridge)".into(),
problem: BridgeParseError::BridgesNotSupported.to_string(),
})
}
}
impl FromStr for BridgeConfigBuilder {
type Err = BridgeParseError;
fn from_str(_: &str) -> Result<BridgeConfigBuilder, BridgeParseError> {
Err(BridgeParseError::BridgesNotSupported)
}
}

View File

@ -19,6 +19,13 @@ define_accessor_trait! {
/// Should the bridges be used?
///
/// This is only allowed to return true if `bridges()` is nonempty.
///
/// Therefore, it also requires `tor-guardmgr` cargo feature `bridge-client`,
/// since without that feature `BridgeConfig` is uninhabited and therefore
/// `bridges` is necessarily empty.
//
// Therefore, it is safe (from a "reject unsupported config" point of view)
// to ctest this only in code which is #[cfg(feature = "bridge-client")].
fn bridges_enabled(&self) -> bool;
}
}

View File

@ -107,7 +107,7 @@ pub(crate) async fn keep_netdir_updated<RT: tor_rtcompat::Runtime>(
DirEvent::NewConsensus | DirEvent::NewDescriptors => {
if let Some(inner) = inner.upgrade() {
let mut inner = inner.lock().expect("Poisoned lock");
inner.update(runtime.wallclock());
inner.update(runtime.wallclock(), runtime.now());
} else {
return;
}
@ -136,7 +136,7 @@ pub(crate) async fn keep_bridge_descs_updated<RT: tor_rtcompat::Runtime>(
E::SomethingChanged => {
if let Some(inner) = inner.upgrade() {
let mut inner = inner.lock().expect("Poisoned lock");
inner.update(runtime.wallclock());
inner.update(runtime.wallclock(), runtime.now());
} else {
return;
}

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::time::{Duration, Instant, SystemTime};
use tracing::{trace, warn};
use tracing::{info, trace, warn};
use crate::dirstatus::DirStatus;
use crate::sample::Candidate;
@ -15,6 +15,7 @@ use crate::skew::SkewObservation;
use crate::util::randomize_time;
use crate::{ids::GuardId, GuardParams, GuardRestriction, GuardUsage};
use crate::{sample, ExternalActivity, GuardSetSelector, GuardUsageKind};
use safelog::sensitive as sv;
use tor_linkspec::{
ChanTarget, ChannelMethod, HasAddrs, HasChanMethod, HasRelayIds, PtTarget, RelayIds,
};
@ -29,16 +30,15 @@ pub(crate) enum Reachable {
/// used it more recently than we've failed.
Reachable,
/// A guard is believed to be unreachable, since recent attempts
/// to use it have failed.
/// to use it have failed, and not enough time has elapsed since then.
Unreachable,
/// A guard's reachability status is unknown.
///
/// The status might be unknown for a variety of reasons, including:
/// * We haven't tried to use the guard.
/// * Attempts to use it have failed, but those attempts are far
/// enough in the past that we're willing to retry them.
/// We have never (during the lifetime of the current guard manager)
/// tried to connect to this guard.
#[educe(Default)]
Unknown,
Untried,
/// The last time that we tried to connect to this guard, it failed,
/// but enough time has elapsed that we think it is worth trying again.
Retriable,
}
/// The name and version of the crate that first picked a potential
@ -298,7 +298,7 @@ impl Guard {
unlisted_since: None,
dir_info_missing: false,
last_tried_to_connect_at: None,
reachable: Reachable::Unknown,
reachable: Reachable::Untried,
retry_at: None,
dir_status: guard_dirstatus(),
retry_schedule: None,
@ -412,7 +412,22 @@ impl Guard {
/// Change the reachability status for this guard.
fn set_reachable(&mut self, r: Reachable) {
use Reachable as R;
if self.reachable != r {
// High-level logs, if change is interesting to user.
match (self.reachable, r) {
(_, R::Reachable) => info!(
"We have found that {} is usable.",
sv(self.display_chan_target())
),
(R::Untried | R::Reachable, R::Unreachable) => warn!(
"Could not connect to {}. We'll retry later, and let you know if it succeeds.",
sv(self.display_chan_target())
),
(_, _) => {} // not interesting.
}
//
trace!(guard_id = ?self.id, old=?self.reachable, new=?r, "Guard status changed.");
self.reachable = r;
}
@ -453,10 +468,10 @@ impl Guard {
}
/// If this guard is marked Unreachable, clear its unreachability status
/// and mark it as Unknown.
/// and mark it as Retriable.
pub(crate) fn mark_retriable(&mut self) {
if self.reachable != Reachable::Reachable {
self.set_reachable(Reachable::Unknown);
if self.reachable == Reachable::Unreachable {
self.set_reachable(Reachable::Retriable);
self.retry_at = None;
self.retry_schedule = None;
}
@ -919,7 +934,7 @@ mod test {
assert_eq!(g.guard_id(), &id);
assert!(g.same_relay_ids(&FirstHopId::in_sample(GuardSetSelector::Default, id)));
assert_eq!(g.addrs(), &["127.0.0.7:7777".parse().unwrap()]);
assert_eq!(g.reachable(), Reachable::Unknown);
assert_eq!(g.reachable(), Reachable::Untried);
assert_eq!(g.reachable(), Reachable::default());
use crate::GuardUsageBuilder;
@ -1077,7 +1092,7 @@ mod test {
// Retriable right after the retry time.
g.consider_retry(g.retry_at.unwrap() + Duration::from_secs(1));
assert!(g.retry_at.is_none());
assert_eq!(g.reachable(), Reachable::Unknown);
assert_eq!(g.reachable(), Reachable::Retriable);
}
#[test]
@ -1290,11 +1305,11 @@ mod test {
let mut g = basic_guard();
use super::Reachable::*;
assert_eq!(g.reachable(), Unknown);
assert_eq!(g.reachable(), Untried);
for (pre, post) in &[
(Unknown, Unknown),
(Unreachable, Unknown),
(Untried, Untried),
(Unreachable, Retriable),
(Reachable, Reachable),
] {
g.reachable = *pre;

View File

@ -82,18 +82,8 @@ mod skew;
mod util;
#[cfg(not(feature = "bridge-client"))]
/// Bridges (stub module, bridges disabled in cargo features)
pub mod bridge {
/// Configuration for a bridge - uninhabited placeholder type
///
/// This type appears in configuration APIs as a stand-in,
/// when the `bridge-client` cargo feature is not enabled.
///
/// The type is uninhabited: without this feature, you cannot create a `BridgeConfig`.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum BridgeConfig {}
}
#[path = "bridge_disabled.rs"]
pub mod bridge;
#[cfg(feature = "testing")]
pub use config::testing::TestConfig;
@ -347,7 +337,7 @@ impl<R: Runtime> GuardMgr<R> {
let mut inner = inner.lock().expect("lock poisoned");
// TODO(nickm): This calls `GuardMgrInner::update`. Will we mind doing so before any
// providers are configured? I think not, but we should make sure.
inner.replace_bridge_config(config, SystemTime::now());
inner.replace_bridge_config(config, runtime.wallclock(), runtime.now());
}
{
let weak_inner = Arc::downgrade(&inner);
@ -457,8 +447,7 @@ impl<R: Runtime> GuardMgr<R> {
pub fn reload_persistent_state(&self) -> Result<(), GuardMgrError> {
let mut inner = self.inner.lock().expect("Poisoned lock");
if let Some(new_guards) = inner.storage.load()? {
let now = self.runtime.wallclock();
inner.replace_guards_with(new_guards, now);
inner.replace_guards_with(new_guards, self.runtime.wallclock(), self.runtime.now());
}
Ok(())
}
@ -470,8 +459,9 @@ impl<R: Runtime> GuardMgr<R> {
let mut inner = self.inner.lock().expect("Poisoned lock");
debug_assert!(inner.storage.can_store());
let new_guards = inner.storage.load()?.unwrap_or_default();
let now = self.runtime.wallclock();
inner.replace_guards_with(new_guards, now);
let wallclock = self.runtime.wallclock();
let now = self.runtime.now();
inner.replace_guards_with(new_guards, wallclock, now);
Ok(())
}
@ -507,14 +497,15 @@ impl<R: Runtime> GuardMgr<R> {
#[cfg(any(test, feature = "testing"))]
pub fn install_test_netdir(&self, netdir: &NetDir) {
use tor_netdir::testprovider::TestNetDirProvider;
let now = self.runtime.wallclock();
let wallclock = self.runtime.wallclock();
let now = self.runtime.now();
let netdir_provider: Arc<dyn NetDirProvider> =
Arc::new(TestNetDirProvider::from(netdir.clone()));
self.install_netdir_provider(&netdir_provider)
.expect("Couldn't install testing network provider");
let mut inner = self.inner.lock().expect("Poisoned lock");
inner.update(now);
inner.update(wallclock, now);
}
/// Replace the configuration in this `GuardMgr` with `config`.
@ -528,16 +519,21 @@ impl<R: Runtime> GuardMgr<R> {
}
// If we are built to use bridges, change the bridge configuration.
#[cfg(feature = "bridge-client")]
inner.replace_bridge_config(config, SystemTime::now());
{
let wallclock = self.runtime.wallclock();
let now = self.runtime.now();
inner.replace_bridge_config(config, wallclock, now);
}
Ok(())
}
/// Replace the current [`GuardFilter`] used by this `GuardMgr`.
// TODO should this be part of the config?
pub fn set_filter(&self, filter: GuardFilter) {
let now = self.runtime.wallclock();
let wallclock = self.runtime.wallclock();
let now = self.runtime.now();
let mut inner = self.inner.lock().expect("Poisoned lock");
inner.set_filter(filter, now);
inner.set_filter(filter, wallclock, now);
}
/// Select a guard for a given [`GuardUsage`].
@ -833,7 +829,7 @@ impl GuardMgrInner {
/// Update the status of all guards in the active set, based on the passage
/// of time, our configuration, and and the relevant Universe for our active
/// set.
fn update(&mut self, now: SystemTime) {
fn update(&mut self, wallclock: SystemTime, now: Instant) {
self.with_opt_netdir(|this, netdir| {
// Here we update our parameters from the latest NetDir, and check
// whether we need to change to a (non)-restrictive GuardSet based
@ -850,19 +846,26 @@ impl GuardMgrInner {
// BridgeSet—depending on what the GuardSet wants.
Self::update_guardset_internal(
&this.params,
now,
wallclock,
this.guards.active_set.universe_type(),
this.guards.active_guards_mut(),
univ,
);
#[cfg(feature = "bridge-client")]
this.update_desired_descriptors(Instant::now()); // TODO pt-client: take inst as an argument.
this.update_desired_descriptors(now);
#[cfg(not(feature = "bridge-client"))]
let _ = now;
});
}
/// Replace our bridge configuration with the one from `new_config`.
#[cfg(feature = "bridge-client")]
fn replace_bridge_config(&mut self, new_config: &impl GuardMgrConfig, now: SystemTime) {
fn replace_bridge_config(
&mut self,
new_config: &impl GuardMgrConfig,
wallclock: SystemTime,
now: Instant,
) {
match (&self.configured_bridges, new_config.bridges_enabled()) {
(None, false) => {
assert_ne!(
@ -891,7 +894,7 @@ impl GuardMgrInner {
// If we have gotten here, we have changed the set of bridges, changed
// which set is active, or changed them both. We need to make sure that
// our `GuardSet` object is up-to-date with our configuration.
self.update(now);
self.update(wallclock, now);
}
/// Update our parameters, our selection (based on network parameters and
@ -1016,10 +1019,15 @@ impl GuardMgrInner {
/// Replace the active guard state with `new_state`, preserving
/// non-persistent state for any guards that are retained.
fn replace_guards_with(&mut self, mut new_guards: GuardSets, now: SystemTime) {
fn replace_guards_with(
&mut self,
mut new_guards: GuardSets,
wallclock: SystemTime,
now: Instant,
) {
std::mem::swap(&mut self.guards, &mut new_guards);
self.guards.copy_status_from(new_guards);
self.update(now);
self.update(wallclock, now);
}
/// Update which guard set is active based on the current filter and the
@ -1089,9 +1097,9 @@ impl GuardMgrInner {
}
/// Replace the current GuardFilter with `filter`.
fn set_filter(&mut self, filter: GuardFilter, now: SystemTime) {
fn set_filter(&mut self, filter: GuardFilter, wallclock: SystemTime, now: Instant) {
self.filter = filter;
self.update(now);
self.update(wallclock, now);
}
/// Called when the circuit manager reports (via [`GuardMonitor`]) that
@ -1371,7 +1379,7 @@ impl GuardMgrInner {
/// Run any periodic events that update guard status, and return a
/// duration after which periodic events should next be run.
pub(crate) fn run_periodic_events(&mut self, wallclock: SystemTime, now: Instant) -> Duration {
self.update(wallclock);
self.update(wallclock, now);
self.expire_and_answer_pending_requests(now);
Duration::from_secs(1) // TODO: Too aggressive.
}

View File

@ -825,8 +825,10 @@ impl GuardSet {
match (src, guard.reachable()) {
(_, Reachable::Reachable) => return Some(false),
(_, Reachable::Unreachable) => (),
(ListKind::Primary, Reachable::Unknown) => return Some(false),
(_, Reachable::Unknown) => {
(ListKind::Primary, Reachable::Untried | Reachable::Retriable) => {
return Some(false)
}
(_, Reachable::Untried | Reachable::Retriable) => {
if guard.exploratory_attempt_after(cutoff) {
return None;
}
@ -1501,7 +1503,7 @@ mod test {
let g2 = guards1.get(&id2).unwrap();
assert!(g1.confirmed());
assert!(!g2.confirmed());
assert_eq!(g1.reachable(), Reachable::Unknown);
assert_eq!(g1.reachable(), Reachable::Untried);
assert_eq!(g2.reachable(), Reachable::Unreachable);
}

View File

@ -13,7 +13,8 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = []
experimental = ["pt-client"]
full = ["pt-client"]
experimental = []
pt-client = []
[dependencies]

View File

@ -40,16 +40,20 @@ TODO: Possibly the link specifiers and the `*Target` traits belong in different
## Compile-time features
* `pt-client` -- Build with enhanced data types to support pluggable
transports.
* `full` -- Build with all the features above.
### Experimental and unstable features
Note that the APIs enabled by these features are NOT covered by
semantic versioning[^1] guarantees: we might break them or remove
them between patch versions.
* `pt-client` -- Build with enhanced data types to support pluggable
transports.
* `experimental` -- Build with all experimental features above.
* `experimental` -- Build with all experimental features above. (Currently,
there are no experimental features in this crate, but there may be in the
future.)
[^1]: Remember, semantic versioning is what makes various `cargo`
features work reliably. To be explicit: if you want `cargo update`

View File

@ -6,4 +6,6 @@ MODIFIED: RelayId and RelayIdRef now implement Ord.
MODIFIED: Added cmp_by_relay_ids() to HasRelayIds.
BREAKING: Replaced functions to access addresses from ChanMethod.
BREAKING: Replaced functions to strip addresses from ChanMethod.
BREAKING: Remove impl Display for OwnedCircTarget.
ADDED: Provide deconstructors for PtTargetSettings and PtTarget
MODIFIED: Renaming PtTargetAddr to BridgeAddr (in progress, will become BREAKING)

View File

@ -50,12 +50,20 @@ pub use ids::{
};
pub use ls::LinkSpec;
pub use owned::{
OwnedChanTarget, OwnedChanTargetBuilder, OwnedCircTarget, OwnedCircTargetBuilder, RelayIds,
RelayIdsBuilder,
IntoOwnedChanTarget, OwnedChanTarget, OwnedChanTargetBuilder, OwnedCircTarget,
OwnedCircTargetBuilder, RelayIds, RelayIdsBuilder,
};
pub use traits::{
ChanTarget, CircTarget, DirectChanMethodsHelper, HasAddrs, HasChanMethod, HasRelayIds,
HasRelayIdsLegacy,
};
pub use transport::{ChannelMethod, PtAddrError, PtTargetAddr, TransportId, TransportIdError};
pub use transport::{BridgeAddr, BridgeAddrError, ChannelMethod, TransportId, TransportIdError};
pub use transport::{PtTarget, PtTargetInvalidSetting, PtTargetSettings, PtTransportName};
/// Deprecated alias for `BridgeAddr`
// TODO pt-client remove this alias
pub type PtTargetAddr = BridgeAddr;
/// Deprecated alias for `BridgeAddr`
// TODO pt-client remove this alias
pub type PtAddrError = BridgeAddrError;

View File

@ -159,17 +159,7 @@ impl OwnedChanTarget {
/// Primarily for error reporting and logging
impl Display for OwnedChanTarget {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[")?;
match &*self.addrs {
[] => write!(f, "?")?,
[a] => write!(f, "{}", a)?,
[a, ..] => write!(f, "{}+", a)?,
};
for ident in self.identities() {
write!(f, " {}", ident)?;
}
write!(f, "]")?;
Ok(())
write!(f, "{}", self.display_chan_target())
}
}
@ -209,13 +199,6 @@ impl OwnedCircTarget {
}
}
/// Primarily for error reporting and logging
impl Display for OwnedCircTarget {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.chan_target, f)
}
}
impl HasAddrs for OwnedCircTarget {
fn addrs(&self) -> &[SocketAddr] {
self.chan_target.addrs()
@ -244,6 +227,24 @@ impl CircTarget for OwnedCircTarget {
}
}
/// A value that can be converted into an OwnedChanTarget.
pub trait IntoOwnedChanTarget {
/// Convert this value into an [`OwnedChanTarget`].
fn to_owned(self) -> OwnedChanTarget;
}
impl<'a, T: ChanTarget + ?Sized> IntoOwnedChanTarget for &'a T {
fn to_owned(self) -> OwnedChanTarget {
OwnedChanTarget::from_chan_target(self)
}
}
impl IntoOwnedChanTarget for OwnedChanTarget {
fn to_owned(self) -> OwnedChanTarget {
self
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]

View File

@ -1,11 +1,11 @@
//! Declare traits to be implemented by types that describe a place
//! that Tor can connect to, directly or indirectly.
use std::{iter::FusedIterator, net::SocketAddr};
use std::{fmt, iter::FusedIterator, net::SocketAddr};
use strum::IntoEnumIterator;
use tor_llcrypto::pk;
use crate::{ChannelMethod, OwnedChanTarget, RelayIdRef, RelayIdType, RelayIdTypeIter};
use crate::{ChannelMethod, RelayIdRef, RelayIdType, RelayIdTypeIter};
/// Legacy implementation helper for HasRelayIds.
///
@ -195,11 +195,17 @@ impl<D: DirectChanMethodsHelper> HasChanMethod for D {
/// Anything that implements 'ChanTarget' can be used as the
/// identity of a relay for the purposes of launching a new
/// channel.
pub trait ChanTarget: HasRelayIds + HasAddrs + HasChanMethod {}
impl<T: ChanTarget> From<&T> for OwnedChanTarget {
fn from(target: &T) -> Self {
OwnedChanTarget::from_chan_target(target)
pub trait ChanTarget: HasRelayIds + HasAddrs + HasChanMethod {
/// Return a reference to this object suitable for formatting its
/// [`ChanTarget`]-specific members.
///
/// The display format is not exhaustive, but tries to give enough
/// information to identify which channel target we're talking about.
fn display_chan_target(&self) -> DisplayChanTarget<'_, Self>
where
Self: Sized,
{
DisplayChanTarget { inner: self }
}
}
@ -226,6 +232,44 @@ pub trait CircTarget: ChanTarget {
fn protovers(&self) -> &tor_protover::Protocols;
}
/// A reference to a ChanTarget that implements Display using a hopefully useful
/// format.
#[derive(Debug, Clone)]
pub struct DisplayChanTarget<'a, T> {
/// The ChanTarget that we're formatting.
inner: &'a T,
}
impl<'a, T: ChanTarget> fmt::Display for DisplayChanTarget<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[")?;
// We look at the chan_method() (where we would connect to) rather than
// the addrs() (where the relay is, nebulously, "located"). This lets us
// give a less surprising description.
match self.inner.chan_method() {
ChannelMethod::Direct(v) if v.is_empty() => write!(f, "?")?,
ChannelMethod::Direct(v) if v.len() == 1 => write!(f, "{}", v[0])?,
ChannelMethod::Direct(v) => write!(f, "{}+", v[0])?,
#[cfg(feature = "pt-client")]
ChannelMethod::Pluggable(target) => {
match target.addr() {
crate::PtTargetAddr::None => {}
other => write!(f, "{} ", other)?,
}
write!(f, "via {}", target.transport())?;
// This deliberately doesn't include the PtTargetSettings, since
// they can be large, and they're typically unnecessary.
}
}
for ident in self.inner.identities() {
write!(f, " {}", ident)?;
}
write!(f, "]")
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
@ -383,4 +427,34 @@ mod test {
b(Some(ed1), Some(rsa1)),
]);
}
#[test]
fn display() {
let e1 = example();
assert_eq!(
e1.display_chan_target().to_string(),
"[127.0.0.1:99+ ed25519:/FHNjmIYoaONpH7QAjDwWAgW7RO6MwOsXeuRFUiQgCU \
$1234567890abcdef12341234567890abcdef1234]"
);
#[cfg(feature = "pt-client")]
{
use crate::PtTarget;
let rsa = hex!("234461644a6f6b6523436f726e794f6e4d61696e").into();
let mut b = crate::OwnedChanTarget::builder();
b.ids().rsa_identity(rsa);
let e2 = b
.method(ChannelMethod::Pluggable(PtTarget::new(
"obfs4".parse().unwrap(),
"127.0.0.1:99".parse().unwrap(),
)))
.build()
.unwrap();
assert_eq!(
e2.to_string(),
"[127.0.0.1:99 via obfs4 $234461644a6f6b6523436f726e794f6e4d61696e]"
);
}
}
}

View File

@ -21,6 +21,12 @@ use crate::HasAddrs;
/// If this crate is compiled with the `pt-client` feature, this type can
/// support pluggable transports; otherwise, only the built-in transport type is
/// supported.
///
/// This can be displayed as, or parsed from, a string.
/// `"-"` is used to indicate the builtin transport,
/// and `""` and `"bridge"` and `"<none>"` are also recognised for that.
//
// We recognise "bridge" as pluggable; "BRIDGE" is rejected as invalid.
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
pub struct TransportId(Inner);
@ -95,17 +101,20 @@ impl Display for PtTransportName {
}
}
/// This identifier is used to indicate the built-in transport.
/// These identifiers are used to indicate the built-in transport.
///
/// When outputting string representations, the first (`"-"`) is used.
//
// Actual pluggable transport names are restricted to the syntax of C identifiers.
// This string deliberately is not in that syntax so as to avoid clashes.
const BUILT_IN_ID: &str = "<none>";
// These strings are deliberately not in that syntax so as to avoid clashes.
// `"bridge"` is likewise prohibited by the spec.
const BUILT_IN_IDS: &[&str] = &["-", "", "bridge", "<none>"];
impl FromStr for TransportId {
type Err = TransportIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == BUILT_IN_ID {
if BUILT_IN_IDS.contains(&s) {
return Ok(TransportId(Inner::BuiltIn));
};
@ -123,7 +132,7 @@ impl FromStr for TransportId {
impl Display for TransportId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Inner::BuiltIn => write!(f, "{}", BUILT_IN_ID),
Inner::BuiltIn => write!(f, "{}", BUILT_IN_IDS[0]),
#[cfg(feature = "pt-client")]
Inner::Pluggable(name) => write!(f, "{}", name),
}
@ -171,10 +180,51 @@ pub enum TransportIdError {
}
impl TransportId {
/// Return a new `TransportId` referencing the builtin transport
///
/// This is equivalent to the `Default` impl.
pub fn new_builtin() -> Self {
TransportId(Inner::BuiltIn)
}
/// Return a new `TransportId` referencing a pluggable transport
///
/// This is equivalent to the `From<PtTransportName>` impl.
#[cfg(feature = "pt-client")]
pub fn new_pluggable(pt: PtTransportName) -> Self {
pt.into()
}
/// Return true if this is the built-in transport.
pub fn is_builtin(&self) -> bool {
self.0 == Inner::BuiltIn
}
/// Returns the pluggable transport name
///
/// Or `None` if `self` doesn't specify a pluggable transport
/// (e.g. if it specifies the builtin transport).
#[cfg(feature = "pt-client")]
pub fn as_pluggable(&self) -> Option<&PtTransportName> {
match &self.0 {
Inner::BuiltIn => None,
#[cfg(feature = "pt-client")]
Inner::Pluggable(pt) => Some(pt),
}
}
/// Consumes this `TransportId` and returns the pluggable transport name
///
/// Or `None` if `self` doesn't specify a pluggable transport
/// (e.g. if it specifies the builtin transport).
#[cfg(feature = "pt-client")]
pub fn into_pluggable(self) -> Option<PtTransportName> {
match self.0 {
Inner::BuiltIn => None,
#[cfg(feature = "pt-client")]
Inner::Pluggable(pt) => Some(pt),
}
}
}
/// This identifier is used to indicate no transport address.
@ -188,7 +238,7 @@ const NONE_ADDR: &str = "-";
Clone, Debug, PartialEq, Eq, Hash, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
)]
#[non_exhaustive]
pub enum PtTargetAddr {
pub enum BridgeAddr {
/// An IP address and port for a Tor relay.
///
/// This is the only address type supported by the BuiltIn transport.
@ -199,10 +249,10 @@ pub enum PtTargetAddr {
None,
}
/// An error from parsing a [`PtTargetAddr`].
/// An error from parsing a [`BridgeAddr`].
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PtAddrError {
pub enum BridgeAddrError {
/// We were compiled without support for addresses of this type.
#[error("Not compiled with pluggable transport support.")]
NoSupport,
@ -211,32 +261,32 @@ pub enum PtAddrError {
BadAddress(String),
}
impl FromStr for PtTargetAddr {
type Err = PtAddrError;
impl FromStr for BridgeAddr {
type Err = BridgeAddrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(addr) = s.parse() {
Ok(PtTargetAddr::IpPort(addr))
Ok(BridgeAddr::IpPort(addr))
} else if let Some((name, port)) = s.rsplit_once(':') {
let port = port
.parse()
.map_err(|_| PtAddrError::BadAddress(s.to_string()))?;
.map_err(|_| BridgeAddrError::BadAddress(s.to_string()))?;
Ok(Self::HostPort(name.to_string(), port))
} else if s == NONE_ADDR {
Ok(Self::None)
} else {
Err(PtAddrError::BadAddress(s.to_string()))
Err(BridgeAddrError::BadAddress(s.to_string()))
}
}
}
impl Display for PtTargetAddr {
impl Display for BridgeAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PtTargetAddr::IpPort(addr) => write!(f, "{}", addr),
PtTargetAddr::HostPort(host, port) => write!(f, "{}:{}", host, port),
PtTargetAddr::None => write!(f, "{}", NONE_ADDR),
BridgeAddr::IpPort(addr) => write!(f, "{}", addr),
BridgeAddr::HostPort(host, port) => write!(f, "{}:{}", host, port),
BridgeAddr::None => write!(f, "{}", NONE_ADDR),
}
}
}
@ -285,6 +335,11 @@ impl PtTargetSettings {
self.settings.push((k, v));
Ok(())
}
/// Return the inner list of (key, value) pairs
pub fn into_inner(self) -> Vec<(String, String)> {
self.settings
}
}
impl TryFrom<Vec<(String, String)>> for PtTargetSettings {
@ -311,7 +366,7 @@ pub struct PtTarget {
/// The transport to be used.
transport: PtTransportName,
/// The address of the bridge relay, if any.
addr: PtTargetAddr,
addr: BridgeAddr,
/// Any additional settings used by the transport.
#[serde(default)]
settings: PtTargetSettings,
@ -338,7 +393,7 @@ pub enum PtTargetInvalidSetting {
impl PtTarget {
/// Create a new `PtTarget` (with no settings)
pub fn new(transport: PtTransportName, addr: PtTargetAddr) -> Self {
pub fn new(transport: PtTransportName, addr: BridgeAddr) -> Self {
PtTarget {
transport,
addr,
@ -361,7 +416,7 @@ impl PtTarget {
}
/// Get the transport target address (or host and port)
pub fn addr(&self) -> &PtTargetAddr {
pub fn addr(&self) -> &BridgeAddr {
&self.addr
}
@ -382,13 +437,18 @@ impl PtTarget {
pub fn socket_addrs(&self) -> Option<&[std::net::SocketAddr]> {
match self {
PtTarget {
addr: PtTargetAddr::IpPort(addr),
addr: BridgeAddr::IpPort(addr),
..
} => Some(std::slice::from_ref(addr)),
_ => None,
}
}
/// Consume the `PtTarget` and return the component parts
pub fn into_parts(self) -> (PtTransportName, BridgeAddr, PtTargetSettings) {
(self.transport, self.addr, self.settings)
}
}
/// The way to approach a single relay in order to open a channel.
@ -426,10 +486,10 @@ impl ChannelMethod {
}
}
/// Return a PtTargetAddr that this ChannelMethod uses.
pub fn target_addr(&self) -> Option<PtTargetAddr> {
/// Return a BridgeAddr that this ChannelMethod uses.
pub fn target_addr(&self) -> Option<BridgeAddr> {
match self {
ChannelMethod::Direct(addr) if !addr.is_empty() => Some(PtTargetAddr::IpPort(addr[0])),
ChannelMethod::Direct(addr) if !addr.is_empty() => Some(BridgeAddr::IpPort(addr[0])),
#[cfg(feature = "pt-client")]
ChannelMethod::Pluggable(PtTarget { addr, .. }) => Some(addr.clone()),
@ -464,7 +524,7 @@ impl ChannelMethod {
P: Fn(&std::net::SocketAddr) -> bool,
{
#[cfg(feature = "pt-client")]
use PtTargetAddr as Pt;
use BridgeAddr as Pt;
match self {
ChannelMethod::Direct(d) if d.is_empty() => {}
@ -513,11 +573,11 @@ pub enum RetainAddrsError {
NoAddrsLeft,
}
impl HasAddrs for PtTargetAddr {
impl HasAddrs for BridgeAddr {
fn addrs(&self) -> &[SocketAddr] {
match self {
PtTargetAddr::IpPort(sockaddr) => slice::from_ref(sockaddr),
PtTargetAddr::HostPort(..) | PtTargetAddr::None => &[],
BridgeAddr::IpPort(sockaddr) => slice::from_ref(sockaddr),
BridgeAddr::HostPort(..) | BridgeAddr::None => &[],
}
}
}
@ -574,20 +634,28 @@ mod test {
let obfs = TransportId::from_str("obfs4").unwrap();
let dflt = TransportId::default();
let dflt2 = TransportId::from_str("<none>").unwrap();
let dflt3 = TransportId::from_str("-").unwrap();
let dflt4 = TransportId::from_str("").unwrap();
let dflt5 = TransportId::from_str("bridge").unwrap();
let snow = TransportId::from_str("snowflake").unwrap();
let obfs_again = TransportId::from_str("obfs4").unwrap();
assert_eq!(obfs, obfs_again);
assert_eq!(dflt, dflt2);
assert_eq!(dflt, dflt3);
assert_eq!(dflt, dflt4);
assert_eq!(dflt, dflt5);
assert_ne!(snow, obfs);
assert_ne!(snow, dflt);
assert_eq!(dflt.to_string(), "-");
assert!(matches!(
TransportId::from_str("12345"),
Err(TransportIdError::BadId(_))
));
assert!(matches!(
TransportId::from_str("bridge"),
TransportId::from_str("Bridge"),
Err(TransportIdError::BadId(_))
));
}
@ -595,7 +663,7 @@ mod test {
#[test]
fn addr() {
for addr in &["1.2.3.4:555", "[::1]:9999"] {
let a: PtTargetAddr = addr.parse().unwrap();
let a: BridgeAddr = addr.parse().unwrap();
assert_eq!(&a.to_string(), addr);
let sa: SocketAddr = addr.parse().unwrap();
@ -603,14 +671,14 @@ mod test {
}
for addr in &["www.example.com:9100", "-"] {
let a: PtTargetAddr = addr.parse().unwrap();
let a: BridgeAddr = addr.parse().unwrap();
assert_eq!(&a.to_string(), addr);
assert_eq!(a.addrs(), &[]);
}
for addr in &["foobar", "<<<>>>"] {
let e = PtTargetAddr::from_str(addr).unwrap_err();
assert!(matches!(e, PtAddrError::BadAddress(_)));
let e = BridgeAddr::from_str(addr).unwrap_err();
assert!(matches!(e, BridgeAddrError::BadAddress(_)));
}
}
}

View File

@ -267,7 +267,7 @@ impl PublicKey {
pub fn to_rsa_identity(&self) -> RsaIdentity {
use crate::d::Sha1;
use digest::Digest;
let id = Sha1::digest(&self.to_der()).into();
let id = Sha1::digest(self.to_der()).into();
RsaIdentity { id }
}
}

View File

@ -58,7 +58,7 @@ impl<'a, K: Keyword> TokVal<'a, K> {
/// Return the Item for this value, if there is exactly one.
fn singleton(&self) -> Option<&Item<'a, K>> {
match &*self.0 {
&[ref x] => Some(x),
[x] => Some(x),
_ => None,
}
}

View File

@ -652,8 +652,8 @@ impl<'a, K: Keyword> NetDocReader<'a, K> {
/// Give an error if there are remaining tokens in this NetDocReader.
///
/// Like [`should_be_exhausted`], but permit empty lines at the end of the
/// document.
/// Like [`should_be_exhausted`](Self::should_be_exhausted),
/// but permit empty lines at the end of the document.
#[cfg(feature = "routerdesc")]
pub(crate) fn should_be_exhausted_but_for_empty_lines(&mut self) -> Result<()> {
use crate::err::ParseErrorKind as K;

View File

@ -344,7 +344,7 @@ mod test {
assert_eq!(store.try_lock()?, LockStatus::NewlyAcquired);
let fname = statedir.join("numbat.toml");
let fname2 = statedir.join("quoll.json");
std::fs::write(&fname, "we no longer use toml files.").unwrap();
std::fs::write(fname, "we no longer use toml files.").unwrap();
std::fs::write(&fname2, "{}").unwrap();
// Make the store directory read-only and make sure that we can't delete from it.
@ -358,7 +358,7 @@ mod test {
assert_eq!(lst.len(), 3); // We can't remove the file, but we didn't freak out. Great!
// Try failing to read a mode-0 file.
std::fs::set_permissions(&statedir, rw_dir).unwrap();
std::fs::set_permissions(&fname2, unusable).unwrap();
std::fs::set_permissions(fname2, unusable).unwrap();
let h: Result<Option<HashMap<String, u32>>> = store.load("quoll");
assert!(h.is_err());

View File

@ -122,7 +122,7 @@ mod test {
let fname1 = dir.path().join("quokka");
let now = SystemTime::now();
std::fs::write(&fname1, "hello world").unwrap();
std::fs::write(fname1, "hello world").unwrap();
let mut r = std::fs::read_dir(dir.path()).unwrap();
let ent = r.next().unwrap().unwrap();
@ -136,10 +136,10 @@ mod test {
let now = SystemTime::now();
let fname1 = dir.path().join("quokka.toml");
std::fs::write(&fname1, "hello world").unwrap();
std::fs::write(fname1, "hello world").unwrap();
let fname2 = dir.path().join("wombat.json");
std::fs::write(&fname2, "greetings").unwrap();
std::fs::write(fname2, "greetings").unwrap();
let removable_now = files_to_delete(dir.path(), now);
assert!(removable_now.is_empty());

View File

@ -359,8 +359,8 @@ mod tests {
fn make_fake_ephem_key(bytes: &[u8]) -> EphemeralSecret {
assert_eq!(bytes.len(), 32);
let mut rng = FakePRNG::new(bytes);
EphemeralSecret::new(&mut rng)
let rng = FakePRNG::new(bytes);
EphemeralSecret::new(rng)
}
#[test]

View File

@ -13,6 +13,13 @@ set -euo pipefail
# If you add anything to this section, make sure to add a comment
# explaining why it's safe to do so.
IGNORE=(
# This is a real but theoretical unaligned read. It might happen only on
# Windows and only with a custom global allocator, which we don't do in our
# arti binary. The bad crate is depended on by env-logger and clap.
# This is being discussed by those crates' contributors here:
# https://github.com/clap-rs/clap/pull/4249
# https://github.com/rust-cli/env_logger/pull/246
--ignore RUSTSEC-2021-0145
)
${CARGO:-cargo} audit -D warnings "${IGNORE[@]}"