Merge branch 'main' into 'abstract-pt-mgr'
# Conflicts: # crates/tor-chanmgr/src/factory.rs
This commit is contained in:
commit
09092394a8
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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.)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
ADDED: Add extension trait providing BinaryHeap::retain on Stable (as retain_ext)
|
||||
ADDED: Add derive_serde_raw! macro
|
||||
|
|
|
@ -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)*
|
||||
}
|
||||
} }
|
||||
|
|
|
@ -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 = []
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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(),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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![
|
||||
|
|
|
@ -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")]
|
||||
{
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(_)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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[@]}"
|
||||
|
|
Loading…
Reference in New Issue