replace Arc with Box and use dyn-clone

this also removes JoinResult
This commit is contained in:
trinity-1686a 2022-03-12 18:33:25 +01:00
parent 43abd119cf
commit cb00ac677b
4 changed files with 69 additions and 95 deletions

7
Cargo.lock generated
View File

@ -986,6 +986,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dyn-clone"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
[[package]] [[package]]
name = "easy-parallel" name = "easy-parallel"
version = "3.2.0" version = "3.2.0"
@ -3231,6 +3237,7 @@ dependencies = [
"bounded-vec-deque", "bounded-vec-deque",
"derive_builder", "derive_builder",
"downcast-rs", "downcast-rs",
"dyn-clone",
"educe", "educe",
"futures", "futures",
"futures-await-test", "futures-await-test",

View File

@ -7,6 +7,7 @@
use crate::address::IntoTorAddr; use crate::address::IntoTorAddr;
use crate::config::{ClientAddrConfig, StreamTimeoutConfig, TorClientConfig}; use crate::config::{ClientAddrConfig, StreamTimeoutConfig, TorClientConfig};
use tor_circmgr::isolation::Isolation;
use tor_circmgr::{DirInfo, IsolationToken, StreamIsolationBuilder, TargetPort}; use tor_circmgr::{DirInfo, IsolationToken, StreamIsolationBuilder, TargetPort};
use tor_config::MutCfg; use tor_config::MutCfg;
use tor_dirmgr::DirEvent; use tor_dirmgr::DirEvent;
@ -16,7 +17,6 @@ use tor_proto::stream::{DataStream, IpVersionPreference, StreamParameters};
use tor_rtcompat::{PreferredRuntime, Runtime, SleepProviderExt}; use tor_rtcompat::{PreferredRuntime, Runtime, SleepProviderExt};
use educe::Educe; use educe::Educe;
use futures::future::Either;
use futures::lock::Mutex as AsyncMutex; use futures::lock::Mutex as AsyncMutex;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use futures::task::SpawnExt; use futures::task::SpawnExt;
@ -104,41 +104,31 @@ pub enum BootstrapBehavior {
} }
/// Preferences for how to route a stream over the Tor network. /// Preferences for how to route a stream over the Tor network.
#[derive(Debug, Clone)] #[derive(Debug, Default, Clone)]
pub struct StreamPrefs<T = IsolationToken> { pub struct StreamPrefs {
/// What kind of IPv6/IPv4 we'd prefer, and how strongly. /// What kind of IPv6/IPv4 we'd prefer, and how strongly.
ip_ver_pref: IpVersionPreference, ip_ver_pref: IpVersionPreference,
/// How should we isolate connection(s) ? /// How should we isolate connection(s) ?
isolation: StreamIsolationPreference<T>, isolation: StreamIsolationPreference,
/// Whether to return the stream optimistically. /// Whether to return the stream optimistically.
optimistic_stream: bool, optimistic_stream: bool,
} }
impl<T> Default for StreamPrefs<T> {
fn default() -> Self {
StreamPrefs {
ip_ver_pref: Default::default(),
isolation: Default::default(),
optimistic_stream: Default::default(),
}
}
}
/// Record of how we are isolating connections /// Record of how we are isolating connections
#[derive(Debug, Clone, Educe)] #[derive(Debug, Clone, Educe)]
#[educe(Default)] #[educe(Default)]
enum StreamIsolationPreference<T> { enum StreamIsolationPreference {
/// No additional isolation /// No additional isolation
#[educe(Default)] #[educe(Default)]
None, None,
/// Id of the isolation group the connection should be part of /// Id of the isolation group the connection should be part of
/// TODO /// TODO
Explicit(Arc<T>), Explicit(Box<dyn Isolation>),
/// Isolate every connection! /// Isolate every connection!
EveryStream, EveryStream,
} }
impl<T> StreamPrefs<T> { impl StreamPrefs {
/// Construct a new StreamPrefs. /// Construct a new StreamPrefs.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
@ -218,6 +208,20 @@ impl<T> StreamPrefs<T> {
params params
} }
/// Indicate that connections with these preferences should have their own isolation group
///
/// This is a convenience method which creates a fresh [`IsolationToken`]
/// and sets it for these preferences.
///
/// This connection preference is orthogonal to isolation established by
/// [`TorClient::isolated_client`]. Connections made with an `isolated_client` (and its
/// clones) will not share circuits with the original client, even if the same
/// `isolation_group` is specified via the `ConnectionPrefs` in force.
pub fn new_isolation_group(&mut self) -> &mut Self {
self.isolation = StreamIsolationPreference::Explicit(Box::new(IsolationToken::new()));
self
}
/// Indicate which other connections might use the same circuit /// Indicate which other connections might use the same circuit
/// as this one. /// as this one.
/// ///
@ -228,8 +232,8 @@ impl<T> StreamPrefs<T> {
/// [`TorClient::isolated_client`]. Connections made with an `isolated_client` (and its /// [`TorClient::isolated_client`]. Connections made with an `isolated_client` (and its
/// clones) will not share circuits with the original client, even if the same /// clones) will not share circuits with the original client, even if the same
/// `isolation_group` is specified via the `ConnectionPrefs` in force. /// `isolation_group` is specified via the `ConnectionPrefs` in force.
pub fn set_isolation_group(&mut self, isolation_group: T) -> &mut Self { pub fn set_isolation_group<T: Isolation>(&mut self, isolation_group: T) -> &mut Self {
self.isolation = StreamIsolationPreference::Explicit(Arc::new(isolation_group)); self.isolation = StreamIsolationPreference::Explicit(Box::new(isolation_group));
self self
} }
@ -253,34 +257,18 @@ impl<T> StreamPrefs<T> {
/// Return a token to describe which connections might use /// Return a token to describe which connections might use
/// the same circuit as this one. /// the same circuit as this one.
fn isolation_group(&self) -> Option<Either<Arc<T>, IsolationToken>> { fn isolation_group(&self) -> Option<Box<dyn Isolation>> {
use StreamIsolationPreference as SIP; use StreamIsolationPreference as SIP;
match self.isolation { match self.isolation {
SIP::None => None, SIP::None => None,
SIP::Explicit(ref ig) => Some(Either::Left(ig.clone())), SIP::Explicit(ref ig) => Some(ig.clone()),
SIP::EveryStream => Some(Either::Right(IsolationToken::new())), SIP::EveryStream => Some(Box::new(IsolationToken::new())),
} }
} }
// TODO: Add some way to be IPFlexible, and require exit to support both. // TODO: Add some way to be IPFlexible, and require exit to support both.
} }
impl StreamPrefs<IsolationToken> {
/// Indicate that connections with these preferences should have their own isolation group
///
/// This is a convenience method which creates a fresh [`IsolationToken`]
/// and sets it for these preferences.
///
/// This connection preference is orthogonal to isolation established by
/// [`TorClient::isolated_client`]. Connections made with an `isolated_client` (and its
/// clones) will not share circuits with the original client, even if the same
/// `isolation_group` is specified via the `ConnectionPrefs` in force.
pub fn new_isolation_group(&mut self) -> &mut Self {
self.isolation = StreamIsolationPreference::Explicit(Arc::new(IsolationToken::new()));
self
}
}
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
impl TorClient<PreferredRuntime> { impl TorClient<PreferredRuntime> {
/// Bootstrap a connection to the Tor network, using the provided `config`. /// Bootstrap a connection to the Tor network, using the provided `config`.
@ -674,10 +662,10 @@ impl<R: Runtime> TorClient<R> {
/// Note that because Tor prefers to do DNS resolution on the remote /// Note that because Tor prefers to do DNS resolution on the remote
/// side of the network, this function takes its address as a string. /// side of the network, this function takes its address as a string.
/// (See [`TorClient::connect()`] for more information.) /// (See [`TorClient::connect()`] for more information.)
pub async fn connect_with_prefs<A: IntoTorAddr, T: tor_circmgr::isolation::Isolation>( pub async fn connect_with_prefs<A: IntoTorAddr>(
&self, &self,
target: A, target: A,
prefs: &StreamPrefs<T>, prefs: &StreamPrefs,
) -> crate::Result<DataStream> { ) -> crate::Result<DataStream> {
let addr = target.into_tor_addr().map_err(wrap_err)?; let addr = target.into_tor_addr().map_err(wrap_err)?;
addr.enforce_config(&self.addrcfg.get())?; addr.enforce_config(&self.addrcfg.get())?;
@ -713,7 +701,7 @@ impl<R: Runtime> TorClient<R> {
// //
// This function is private just because we're not sure we want to provide this API. // This function is private just because we're not sure we want to provide this API.
// https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/250#note_2771238 // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/250#note_2771238
fn set_stream_prefs(&mut self, connect_prefs: StreamPrefs<IsolationToken>) { fn set_stream_prefs(&mut self, connect_prefs: StreamPrefs) {
self.connect_prefs = connect_prefs; self.connect_prefs = connect_prefs;
} }
@ -722,7 +710,7 @@ impl<R: Runtime> TorClient<R> {
/// Connections made with e.g. [`connect`](TorClient::connect) on the returned handle will use /// Connections made with e.g. [`connect`](TorClient::connect) on the returned handle will use
/// `connect_prefs`. This is a convenience wrapper for `clone` and `set_connect_prefs`. /// `connect_prefs`. This is a convenience wrapper for `clone` and `set_connect_prefs`.
#[must_use] #[must_use]
pub fn clone_with_prefs(&self, connect_prefs: StreamPrefs<IsolationToken>) -> Self { pub fn clone_with_prefs(&self, connect_prefs: StreamPrefs) -> Self {
let mut result = self.clone(); let mut result = self.clone();
result.set_stream_prefs(connect_prefs); result.set_stream_prefs(connect_prefs);
result result
@ -734,10 +722,10 @@ impl<R: Runtime> TorClient<R> {
} }
/// On success, return a list of IP addresses, but use prefs. /// On success, return a list of IP addresses, but use prefs.
pub async fn resolve_with_prefs<T: tor_circmgr::isolation::Isolation>( pub async fn resolve_with_prefs(
&self, &self,
hostname: &str, hostname: &str,
prefs: &StreamPrefs<T>, prefs: &StreamPrefs,
) -> crate::Result<Vec<IpAddr>> { ) -> crate::Result<Vec<IpAddr>> {
let addr = (hostname, 1).into_tor_addr().map_err(wrap_err)?; let addr = (hostname, 1).into_tor_addr().map_err(wrap_err)?;
addr.enforce_config(&self.addrcfg.get()).map_err(wrap_err)?; addr.enforce_config(&self.addrcfg.get()).map_err(wrap_err)?;
@ -765,10 +753,10 @@ impl<R: Runtime> TorClient<R> {
/// Perform a remote DNS reverse lookup with the provided IP address. /// Perform a remote DNS reverse lookup with the provided IP address.
/// ///
/// On success, return a list of hostnames. /// On success, return a list of hostnames.
pub async fn resolve_ptr_with_prefs<T: tor_circmgr::isolation::Isolation>( pub async fn resolve_ptr_with_prefs(
&self, &self,
addr: IpAddr, addr: IpAddr,
prefs: &StreamPrefs<T>, prefs: &StreamPrefs,
) -> crate::Result<Vec<String>> { ) -> crate::Result<Vec<String>> {
let circ = self.get_or_launch_exit_circ(&[], prefs).await?; let circ = self.get_or_launch_exit_circ(&[], prefs).await?;
@ -817,10 +805,10 @@ impl<R: Runtime> TorClient<R> {
/// Get or launch an exit-suitable circuit with a given set of /// Get or launch an exit-suitable circuit with a given set of
/// exit ports. /// exit ports.
async fn get_or_launch_exit_circ<T: tor_circmgr::isolation::Isolation>( async fn get_or_launch_exit_circ(
&self, &self,
exit_ports: &[TargetPort], exit_ports: &[TargetPort],
prefs: &StreamPrefs<T>, prefs: &StreamPrefs,
) -> StdResult<ClientCirc, ErrorDetail> { ) -> StdResult<ClientCirc, ErrorDetail> {
self.wait_for_bootstrap().await?; self.wait_for_bootstrap().await?;
let dir = self let dir = self
@ -836,10 +824,7 @@ impl<R: Runtime> TorClient<R> {
b.owner_token(self.client_isolation); b.owner_token(self.client_isolation);
// Consider stream isolation too, if it's set. // Consider stream isolation too, if it's set.
if let Some(tok) = prefs.isolation_group() { if let Some(tok) = prefs.isolation_group() {
match tok { b.stream_token(tok);
Either::Left(tok) => b.stream_token(tok),
Either::Right(tok) => b.stream_token(Arc::new(tok)),
};
} }
// Failure should be impossible with this builder. // Failure should be impossible with this builder.
b.build().expect("Failed to construct StreamIsolation") b.build().expect("Failed to construct StreamIsolation")

View File

@ -35,6 +35,7 @@ async-trait = "0.1.2"
bounded-vec-deque = "0.1" bounded-vec-deque = "0.1"
derive_builder = "0.11" derive_builder = "0.11"
downcast-rs = "1.2.0" downcast-rs = "1.2.0"
dyn-clone = "1.0.4"
educe = "0.4.6" educe = "0.4.6"
futures = "0.3.14" futures = "0.3.14"
humantime-serde = "1.1.1" humantime-serde = "1.1.1"

View File

@ -1,6 +1,7 @@
//! Code related to tracking what activities a circuit can be used for. //! Code related to tracking what activities a circuit can be used for.
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use dyn_clone::{clone_trait_object, DynClone};
use rand::Rng; use rand::Rng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
@ -106,15 +107,16 @@ impl Display for TargetPorts {
/// This trait is intended to be used in dyn context. To implement it for your own types, /// This trait is intended to be used in dyn context. To implement it for your own types,
/// implement [`IsolationHelper`] instead. /// implement [`IsolationHelper`] instead.
// TODO this trait should probably be sealed so the same-type requirement can't be bypassed // TODO this trait should probably be sealed so the same-type requirement can't be bypassed
pub trait Isolation: Downcast + std::fmt::Debug + Send + Sync + 'static { pub trait Isolation: Downcast + DynClone + std::fmt::Debug + Send + Sync + 'static {
/// Returns if two [`Isolation`] are compatible. /// Returns if two [`Isolation`] are compatible.
fn compatible(&self, other: &dyn Isolation) -> bool; fn compatible(&self, other: &dyn Isolation) -> bool;
/// Join two [`Isolation`] into the intersection of what each allows. /// Join two [`Isolation`] into the intersection of what each allows.
fn join(&self, other: &dyn Isolation) -> JoinResult; fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>>;
} }
impl_downcast!(Isolation); impl_downcast!(Isolation);
clone_trait_object!(Isolation);
impl<T: IsolationHelper + std::fmt::Debug + Send + Sync + 'static> Isolation for T { impl<T: IsolationHelper + Clone + std::fmt::Debug + Send + Sync + 'static> Isolation for T {
fn compatible(&self, other: &dyn Isolation) -> bool { fn compatible(&self, other: &dyn Isolation) -> bool {
if let Some(other) = other.as_any().downcast_ref() { if let Some(other) = other.as_any().downcast_ref() {
self.compatible_same_type(other) self.compatible_same_type(other)
@ -122,39 +124,26 @@ impl<T: IsolationHelper + std::fmt::Debug + Send + Sync + 'static> Isolation for
false false
} }
} }
fn join(&self, other: &dyn Isolation) -> JoinResult {
fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>> {
if let Some(other) = other.as_any().downcast_ref() { if let Some(other) = other.as_any().downcast_ref() {
self.join_same_type(other) self.join_same_type(other)
.map(|res| Box::new(res) as Box<dyn Isolation>)
} else { } else {
JoinResult::NoJoin None
} }
} }
} }
/// Result of an [`Isolation::join`] operation
// rational behind this type: it's not possible to take a `self: &Arc<Self>`, so if the merge would
// result in something identical to `self`, we would need to allocate a new Arc instead of clonning
// the old one.
pub enum JoinResult {
/// The intersection is a new object
New(Arc<dyn Isolation>),
/// The intersection is equals to the `self` param
UseLeft,
/// The intersection is equals to the `other` param
UseRight,
/// No intersection is empty
NoJoin,
}
/// Trait to help implement [`Isolation`] /// Trait to help implement [`Isolation`]
/// ///
/// This trait is essentially the same as [`Isolation`] with static types. You should /// This trait is essentially the same as [`Isolation`] with static types. You should
/// implement this trait for types that can represent isolation between streams. /// implement this trait for types that can represent isolation between streams.
pub trait IsolationHelper { pub trait IsolationHelper: Sized {
/// Returns whether self and other are compatible. /// Returns whether self and other are compatible.
fn compatible_same_type(&self, other: &Self) -> bool; fn compatible_same_type(&self, other: &Self) -> bool;
/// Join self and other into the intersection of what they allows. /// Join self and other into the intersection of what they allows.
fn join_same_type(&self, other: &Self) -> JoinResult; fn join_same_type(&self, other: &Self) -> Option<Self>;
} }
/// A token used to isolate unrelated streams on different circuits. /// A token used to isolate unrelated streams on different circuits.
@ -242,15 +231,11 @@ impl IsolationHelper for IsolationToken {
fn compatible_same_type(&self, other: &Self) -> bool { fn compatible_same_type(&self, other: &Self) -> bool {
self == other self == other
} }
fn join_same_type(&self, other: &Self) -> JoinResult { fn join_same_type(&self, other: &Self) -> Option<Self> {
if self.compatible_same_type(other) { if self.compatible_same_type(other) {
// for IsolationToken, any of the three would be correct, but the last one is probably Some(*self)
// slower.
JoinResult::UseLeft
// JoinResult::UseRight
// JoinResult::New(Arc::new(*self))
} else { } else {
JoinResult::NoJoin None
} }
} }
} }
@ -262,8 +247,8 @@ impl IsolationHelper for IsolationToken {
#[derive(Clone, Debug, derive_builder::Builder)] #[derive(Clone, Debug, derive_builder::Builder)]
pub struct StreamIsolation { pub struct StreamIsolation {
/// Any isolation token set on the stream. /// Any isolation token set on the stream.
#[builder(default = "Arc::new(IsolationToken::no_isolation())")] #[builder(default = "Box::new(IsolationToken::no_isolation())")]
stream_token: Arc<dyn Isolation>, stream_token: Box<dyn Isolation>,
/// Any additional isolation token set on an object that "owns" this /// Any additional isolation token set on an object that "owns" this
/// stream. This is typically owned by a `TorClient`. /// stream. This is typically owned by a `TorClient`.
#[builder(default = "IsolationToken::no_isolation()")] #[builder(default = "IsolationToken::no_isolation()")]
@ -297,19 +282,15 @@ impl StreamIsolation {
if self.owner_token != other.owner_token { if self.owner_token != other.owner_token {
return None; return None;
} }
match self.stream_token.join(other.stream_token.as_ref()) { self.stream_token
JoinResult::New(isolation) => Some(StreamIsolation { .join(other.stream_token.as_ref())
stream_token: isolation, .map(|stream_token| StreamIsolation {
stream_token,
owner_token: self.owner_token, owner_token: self.owner_token,
}), })
JoinResult::UseLeft => Some(self.clone()),
JoinResult::UseRight => Some(other.clone()),
JoinResult::NoJoin => None,
}
} }
} }
impl Eq for StreamIsolation {}
impl PartialEq for StreamIsolation { impl PartialEq for StreamIsolation {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.may_share_circuit(other) self.may_share_circuit(other)
@ -348,7 +329,7 @@ impl ExitPolicy {
/// ///
/// This type should stay internal to the circmgr crate for now: we'll probably /// This type should stay internal to the circmgr crate for now: we'll probably
/// want to refactor it a lot. /// want to refactor it a lot.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq)]
pub(crate) enum TargetCircUsage { pub(crate) enum TargetCircUsage {
/// Use for BEGINDIR-based non-anonymous directory connections /// Use for BEGINDIR-based non-anonymous directory connections
Dir, Dir,
@ -922,7 +903,7 @@ mod test {
let no_isolation = StreamIsolation::no_isolation(); let no_isolation = StreamIsolation::no_isolation();
let no_isolation2 = StreamIsolation::builder() let no_isolation2 = StreamIsolation::builder()
.owner_token(IsolationToken::no_isolation()) .owner_token(IsolationToken::no_isolation())
.stream_token(Arc::new(IsolationToken::no_isolation())) .stream_token(Box::new(IsolationToken::no_isolation()))
.build() .build()
.unwrap(); .unwrap();
assert_eq!(no_isolation.owner_token, no_isolation2.owner_token); assert_eq!(no_isolation.owner_token, no_isolation2.owner_token);
@ -943,7 +924,7 @@ mod test {
let tok = IsolationToken::new(); let tok = IsolationToken::new();
let some_isolation = StreamIsolation::builder().owner_token(tok).build().unwrap(); let some_isolation = StreamIsolation::builder().owner_token(tok).build().unwrap();
let some_isolation2 = StreamIsolation::builder() let some_isolation2 = StreamIsolation::builder()
.stream_token(Arc::new(tok)) .stream_token(Box::new(tok))
.build() .build()
.unwrap(); .unwrap();
assert!(!no_isolation.may_share_circuit(&some_isolation)); assert!(!no_isolation.may_share_circuit(&some_isolation));