Turn StreamIsolation into a separate type.

Now that we have two kinds of isolation tokens (those set on a
stream, and those set by the stream's associated TorClient), we
need a more sophisticated kind of isolation.

This fixes the bug introduced with the previous commit, where
per-stream tokens would override per-TorClient tokens.
This commit is contained in:
Nick Mathewson 2021-10-25 12:32:18 -04:00
parent 16f6ee4b54
commit 47234655ce
3 changed files with 103 additions and 43 deletions

View File

@ -7,7 +7,7 @@
use crate::address::IntoTorAddr; use crate::address::IntoTorAddr;
use crate::config::{ClientAddrConfig, TorClientConfig}; use crate::config::{ClientAddrConfig, TorClientConfig};
use tor_circmgr::{IsolationToken, TargetPort}; use tor_circmgr::{IsolationToken, StreamIsolationBuilder, TargetPort};
use tor_dirmgr::DirEvent; use tor_dirmgr::DirEvent;
use tor_persist::{FsStateMgr, StateMgr}; use tor_persist::{FsStateMgr, StateMgr};
use tor_proto::circuit::{ClientCirc, IpVersionPreference}; use tor_proto::circuit::{ClientCirc, IpVersionPreference};
@ -318,9 +318,17 @@ impl<R: Runtime> TorClient<R> {
) -> Result<Arc<ClientCirc>> { ) -> Result<Arc<ClientCirc>> {
let dir = self.dirmgr.netdir(); let dir = self.dirmgr.netdir();
// XXXX: this isn't what we really want. We'd like to have _both_ let isolation = {
// of these tokens considered. let mut b = StreamIsolationBuilder::new();
let isolation = flags.isolation_group().unwrap_or(self.client_isolation); // Always consider our client_isolation.
b.owner_token(self.client_isolation);
// Consider stream isolation too, if it's set.
if let Some(tok) = flags.isolation_group() {
b.stream_token(tok);
}
// Failure should be impossible with this builder.
b.build().expect("Failed to construct StreamIsolation")
};
let circ = self let circ = self
.circmgr .circmgr

View File

@ -74,7 +74,7 @@ mod timeouts;
mod usage; mod usage;
pub use err::Error; pub use err::Error;
pub use usage::{IsolationToken, TargetPort}; pub use usage::{IsolationToken, StreamIsolation, StreamIsolationBuilder, TargetPort};
pub use config::{ pub use config::{
CircMgrConfig, CircMgrConfigBuilder, CircuitTiming, CircuitTimingBuilder, PathConfig, CircMgrConfig, CircMgrConfigBuilder, CircuitTiming, CircuitTimingBuilder, PathConfig,
@ -254,14 +254,11 @@ impl<R: Runtime> CircMgr<R> {
&self, &self,
netdir: DirInfo<'_>, // TODO: This has to be a NetDir. netdir: DirInfo<'_>, // TODO: This has to be a NetDir.
ports: &[TargetPort], ports: &[TargetPort],
isolation_group: IsolationToken, isolation: StreamIsolation,
) -> Result<Arc<ClientCirc>> { ) -> Result<Arc<ClientCirc>> {
self.expire_circuits(); self.expire_circuits();
let ports = ports.iter().map(Clone::clone).collect(); let ports = ports.iter().map(Clone::clone).collect();
let usage = TargetCircUsage::Exit { let usage = TargetCircUsage::Exit { ports, isolation };
ports,
isolation_group,
};
self.mgr.get_or_launch(&usage, netdir).await self.mgr.get_or_launch(&usage, netdir).await
} }

View File

@ -103,6 +103,43 @@ impl IsolationToken {
} }
} }
/// A set of information about how a stream should be isolated.
///
/// If two streams are isolated from one another, they may not share
/// a circuit.
#[derive(Copy, Clone, Eq, Debug, PartialEq, derive_builder::Builder)]
pub struct StreamIsolation {
/// Any isolation token set on the stream.
#[builder(default = "IsolationToken::no_isolation()")]
stream_token: IsolationToken,
/// Any additional isolation token set on an object that "owns" this
/// stream. This is typically owned by a `TorClient`.
#[builder(default = "IsolationToken::no_isolation()")]
owner_token: IsolationToken,
}
impl StreamIsolation {
/// Construct a new StreamIsolation with no isolation enabled.
pub fn no_isolation() -> Self {
StreamIsolationBuilder::new()
.build()
.expect("Bug constructing StreamIsolation")
}
/// Return a new StreamIsolationBuilder for constructing
/// StreamIsolation objects.
pub fn builder() -> StreamIsolationBuilder {
StreamIsolationBuilder::new()
}
}
impl StreamIsolationBuilder {
/// Construct a builder with no items set.
pub fn new() -> Self {
StreamIsolationBuilder::default()
}
}
impl ExitPolicy { impl ExitPolicy {
/// Make a new exit policy from a given Relay. /// Make a new exit policy from a given Relay.
pub(crate) fn from_relay(relay: &Relay<'_>) -> Self { pub(crate) fn from_relay(relay: &Relay<'_>) -> Self {
@ -140,7 +177,7 @@ pub(crate) enum TargetCircUsage {
/// to support any particular port, but it still needs to be an exit. /// to support any particular port, but it still needs to be an exit.
ports: Vec<TargetPort>, ports: Vec<TargetPort>,
/// Isolation group the circuit shall be part of /// Isolation group the circuit shall be part of
isolation_group: IsolationToken, isolation: StreamIsolation,
}, },
/// For a circuit is only used for the purpose of building it. /// For a circuit is only used for the purpose of building it.
TimeoutTesting, TimeoutTesting,
@ -160,7 +197,7 @@ pub(crate) enum SupportedCircUsage {
policy: ExitPolicy, policy: ExitPolicy,
/// Isolation group the circuit is part of. None when the circuit is not yet assigned to an /// Isolation group the circuit is part of. None when the circuit is not yet assigned to an
/// isolation group. /// isolation group.
isolation_group: Option<IsolationToken>, isolation: Option<StreamIsolation>,
}, },
/// This circuit is not suitable for any usage. /// This circuit is not suitable for any usage.
NoUsage, NoUsage,
@ -188,7 +225,7 @@ impl TargetCircUsage {
} }
TargetCircUsage::Exit { TargetCircUsage::Exit {
ports: p, ports: p,
isolation_group, isolation,
} => { } => {
let (path, mon, usable) = ExitPathBuilder::from_target_ports(p.clone()) let (path, mon, usable) = ExitPathBuilder::from_target_ports(p.clone())
.pick_path(rng, netdir, guards, config)?; .pick_path(rng, netdir, guards, config)?;
@ -199,7 +236,7 @@ impl TargetCircUsage {
path, path,
SupportedCircUsage::Exit { SupportedCircUsage::Exit {
policy, policy,
isolation_group: Some(*isolation_group), isolation: Some(*isolation),
}, },
mon, mon,
usable, usable,
@ -212,7 +249,7 @@ impl TargetCircUsage {
let usage = match policy { let usage = match policy {
Some(policy) if policy.allows_some_port() => SupportedCircUsage::Exit { Some(policy) if policy.allows_some_port() => SupportedCircUsage::Exit {
policy, policy,
isolation_group: None, isolation: None,
}, },
_ => SupportedCircUsage::NoUsage, _ => SupportedCircUsage::NoUsage,
}; };
@ -233,11 +270,11 @@ impl crate::mgr::AbstractSpec for SupportedCircUsage {
( (
Exit { Exit {
policy: p1, policy: p1,
isolation_group: i1, isolation: i1,
}, },
TargetCircUsage::Exit { TargetCircUsage::Exit {
ports: p2, ports: p2,
isolation_group: i2, isolation: i2,
}, },
) => { ) => {
i1.map(|i1| i1 == *i2).unwrap_or(true) i1.map(|i1| i1 == *i2).unwrap_or(true)
@ -255,13 +292,10 @@ impl crate::mgr::AbstractSpec for SupportedCircUsage {
(Dir, TargetCircUsage::Dir) => Ok(()), (Dir, TargetCircUsage::Dir) => Ok(()),
( (
Exit { Exit {
isolation_group: ref mut i1, isolation: ref mut i1,
..
},
TargetCircUsage::Exit {
isolation_group: i2,
.. ..
}, },
TargetCircUsage::Exit { isolation: i2, .. },
) if i1.map(|i1| i1 == *i2).unwrap_or(true) => { ) if i1.map(|i1| i1 == *i2).unwrap_or(true) => {
*i1 = Some(*i2); *i1 = Some(*i2);
Ok(()) Ok(())
@ -335,42 +369,50 @@ mod test {
v4: Arc::new("accept 80,443".parse().unwrap()), v4: Arc::new("accept 80,443".parse().unwrap()),
v6: Arc::new("accept 23".parse().unwrap()), v6: Arc::new("accept 23".parse().unwrap()),
}; };
let isolation_group = IsolationToken::new(); let tok1 = IsolationToken::new();
let isolation_group_2 = IsolationToken::new(); let tok2 = IsolationToken::new();
let isolation = StreamIsolationBuilder::new()
.owner_token(tok1)
.build()
.unwrap();
let isolation2 = StreamIsolationBuilder::new()
.owner_token(tok2)
.build()
.unwrap();
let supp_dir = SupportedCircUsage::Dir; let supp_dir = SupportedCircUsage::Dir;
let targ_dir = TargetCircUsage::Dir; let targ_dir = TargetCircUsage::Dir;
let supp_exit = SupportedCircUsage::Exit { let supp_exit = SupportedCircUsage::Exit {
policy: policy.clone(), policy: policy.clone(),
isolation_group: Some(isolation_group), isolation: Some(isolation),
}; };
let supp_exit_iso2 = SupportedCircUsage::Exit { let supp_exit_iso2 = SupportedCircUsage::Exit {
policy: policy.clone(), policy: policy.clone(),
isolation_group: Some(isolation_group_2), isolation: Some(isolation2),
}; };
let supp_exit_no_iso = SupportedCircUsage::Exit { let supp_exit_no_iso = SupportedCircUsage::Exit {
policy, policy,
isolation_group: None, isolation: None,
}; };
let targ_80_v4 = TargetCircUsage::Exit { let targ_80_v4 = TargetCircUsage::Exit {
ports: vec![TargetPort::ipv4(80)], ports: vec![TargetPort::ipv4(80)],
isolation_group, isolation,
}; };
let targ_80_v4_iso2 = TargetCircUsage::Exit { let targ_80_v4_iso2 = TargetCircUsage::Exit {
ports: vec![TargetPort::ipv4(80)], ports: vec![TargetPort::ipv4(80)],
isolation_group: isolation_group_2, isolation: isolation2,
}; };
let targ_80_23_v4 = TargetCircUsage::Exit { let targ_80_23_v4 = TargetCircUsage::Exit {
ports: vec![TargetPort::ipv4(80), TargetPort::ipv4(23)], ports: vec![TargetPort::ipv4(80), TargetPort::ipv4(23)],
isolation_group, isolation,
}; };
let targ_80_23_mixed = TargetCircUsage::Exit { let targ_80_23_mixed = TargetCircUsage::Exit {
ports: vec![TargetPort::ipv4(80), TargetPort::ipv6(23)], ports: vec![TargetPort::ipv4(80), TargetPort::ipv6(23)],
isolation_group, isolation,
}; };
let targ_999_v6 = TargetCircUsage::Exit { let targ_999_v6 = TargetCircUsage::Exit {
ports: vec![TargetPort::ipv6(999)], ports: vec![TargetPort::ipv6(999)],
isolation_group, isolation,
}; };
assert!(supp_dir.supports(&targ_dir)); assert!(supp_dir.supports(&targ_dir));
@ -397,30 +439,38 @@ mod test {
v6: Arc::new("accept 23".parse().unwrap()), v6: Arc::new("accept 23".parse().unwrap()),
}; };
let isolation_group = IsolationToken::new(); let tok1 = IsolationToken::new();
let isolation_group_2 = IsolationToken::new(); let tok2 = IsolationToken::new();
let isolation = StreamIsolationBuilder::new()
.owner_token(tok1)
.build()
.unwrap();
let isolation2 = StreamIsolationBuilder::new()
.owner_token(tok2)
.build()
.unwrap();
let supp_dir = SupportedCircUsage::Dir; let supp_dir = SupportedCircUsage::Dir;
let targ_dir = TargetCircUsage::Dir; let targ_dir = TargetCircUsage::Dir;
let supp_exit = SupportedCircUsage::Exit { let supp_exit = SupportedCircUsage::Exit {
policy: policy.clone(), policy: policy.clone(),
isolation_group: Some(isolation_group), isolation: Some(isolation),
}; };
let supp_exit_iso2 = SupportedCircUsage::Exit { let supp_exit_iso2 = SupportedCircUsage::Exit {
policy: policy.clone(), policy: policy.clone(),
isolation_group: Some(isolation_group_2), isolation: Some(isolation2),
}; };
let supp_exit_no_iso = SupportedCircUsage::Exit { let supp_exit_no_iso = SupportedCircUsage::Exit {
policy, policy,
isolation_group: None, isolation: None,
}; };
let targ_exit = TargetCircUsage::Exit { let targ_exit = TargetCircUsage::Exit {
ports: vec![TargetPort::ipv4(80)], ports: vec![TargetPort::ipv4(80)],
isolation_group, isolation,
}; };
let targ_exit_iso2 = TargetCircUsage::Exit { let targ_exit_iso2 = TargetCircUsage::Exit {
ports: vec![TargetPort::ipv4(80)], ports: vec![TargetPort::ipv4(80)],
isolation_group: isolation_group_2, isolation: isolation2,
}; };
// not allowed, do nothing // not allowed, do nothing
@ -486,10 +536,15 @@ mod test {
assert!(matches!(u_dir, SupportedCircUsage::Dir)); assert!(matches!(u_dir, SupportedCircUsage::Dir));
assert_eq!(p_dir.len(), 1); assert_eq!(p_dir.len(), 1);
let isolation_group = IsolationToken::new(); let tok1 = IsolationToken::new();
let isolation = StreamIsolationBuilder::new()
.owner_token(tok1)
.build()
.unwrap();
let exit_usage = TargetCircUsage::Exit { let exit_usage = TargetCircUsage::Exit {
ports: vec![TargetPort::ipv4(995)], ports: vec![TargetPort::ipv4(995)],
isolation_group, isolation,
}; };
let (p_exit, u_exit, _, _) = exit_usage let (p_exit, u_exit, _, _) = exit_usage
.build_path(&mut rng, di, guards, &config) .build_path(&mut rng, di, guards, &config)
@ -497,9 +552,9 @@ mod test {
assert!(matches!( assert!(matches!(
u_exit, u_exit,
SupportedCircUsage::Exit { SupportedCircUsage::Exit {
isolation_group: iso, isolation: iso,
.. ..
} if iso == Some(isolation_group) } if iso == Some(isolation)
)); ));
assert!(u_exit.supports(&exit_usage)); assert!(u_exit.supports(&exit_usage));
assert_eq!(p_exit.len(), 3); assert_eq!(p_exit.len(), 3);