net/hosts: move transport mixing logic to one place in HostContainer::mix_host function and perform transport mixing in Connector only

This commit is contained in:
oars
2025-07-26 14:31:11 +03:00
parent 3469f2dd04
commit ce95d31c4d
3 changed files with 101 additions and 206 deletions

View File

@@ -36,7 +36,7 @@ use super::{
settings::Settings,
transport::Dialer,
};
use crate::{system::CondVar, Error, Result};
use crate::{net::hosts::HostContainer, system::CondVar, Error, Result};
/// Create outbound socket connections
pub struct Connector {
@@ -72,48 +72,19 @@ impl Connector {
let nym_socks5_proxy = settings.nym_socks5_proxy.clone();
drop(settings);
let mut endpoint = url.clone();
let scheme = endpoint.scheme();
if mixed_transports.contains(&scheme.to_string()) {
if transports.contains(&"socks5".to_string()) && (scheme == "tcp" || scheme == "tor") {
// Prioritize connection through nym socks5 proxy for tcp endpoint mixing
if scheme == "tcp" && nym_socks5_proxy.is_some() {
endpoint = nym_socks5_proxy.unwrap();
} else if tor_socks5_proxy.is_some() {
endpoint = tor_socks5_proxy.unwrap();
} else {
warn!(target: "net::connector::connect", "Transport mixing is enabled but socks5 proxy is not set");
return Err(Error::ConnectFailed)
}
endpoint.set_path(&format!("{}:{}", url.host().unwrap(), url.port().unwrap()));
endpoint.set_scheme("socks5")?;
} else if transports.contains(&"socks5+tls".to_string()) &&
(scheme == "tcp+tls" || scheme == "tor+tls")
{
// Prioritize connection through nym socks5 proxy for tcp+tls endpoint mixing
if scheme == "tcp+tls" && nym_socks5_proxy.is_some() {
endpoint = nym_socks5_proxy.unwrap();
} else if tor_socks5_proxy.is_some() {
endpoint = tor_socks5_proxy.unwrap();
} else {
warn!(target: "net::connector::connect", "Transport mixing is enabled but socks5 proxy is not set");
return Err(Error::ConnectFailed)
}
endpoint.set_path(&format!("{}:{}", url.host().unwrap(), url.port().unwrap()));
endpoint.set_scheme("socks5+tls")?;
} else if transports.contains(&"tor".to_string()) && scheme == "tcp" {
endpoint.set_scheme("tor")?;
} else if transports.contains(&"tor+tls".to_string()) && scheme == "tcp+tls" {
endpoint.set_scheme("tor+tls")?;
} else if transports.contains(&"nym".to_string()) && scheme == "tcp" {
endpoint.set_scheme("nym")?;
} else if transports.contains(&"nym+tls".to_string()) && scheme == "tcp+tls" {
endpoint.set_scheme("nym+tls")?;
}
}
let endpoint = if let Some(mixed_host) = HostContainer::mix_host(
url.clone(),
&transports,
&mixed_transports,
tor_socks5_proxy,
nym_socks5_proxy,
)
.first()
{
mixed_host.clone()
} else {
url.clone()
};
let dialer = Dialer::new(endpoint.clone(), datastore, Some(i2p_socks5_proxy)).await?;
let timeout = Duration::from_secs(outbound_connect_timeout);

View File

@@ -412,96 +412,9 @@ impl HostContainer {
list.last().cloned()
}
/// Fetch addresses that match the provided transports or acceptable
/// mixed transports. Will return an empty Vector if no such addresses
/// were found.
pub(in crate::net) fn fetch(
&self,
color: HostColor,
transports: &[String],
mixed_transports: &[String],
tor_socks5_proxy: Option<Url>,
nym_socks5_proxy: Option<Url>,
) -> Vec<(Url, u64)> {
trace!(target: "net::hosts::fetch_addrs()", "[START] {color:?}");
let mut hosts = vec![];
let index = color as usize;
// If transport mixing is enabled, then for example we're allowed to
// use tor:// to connect to tcp:// and tor+tls:// to connect to tcp+tls://.
// However, **do not** mix tor:// and tcp+tls://, nor tor+tls:// and tcp://.
macro_rules! mix_transport {
($a:expr, $b:expr) => {
if transports.contains(&$a.to_string()) &&
mixed_transports.contains(&$b.to_string())
{
let mut a_to_b = self.fetch_with_schemes(index, &[$b.to_string()], None);
for (addr, last_seen) in a_to_b.iter_mut() {
addr.set_scheme($a).unwrap();
hosts.push((addr.clone(), last_seen.clone()));
}
}
};
}
macro_rules! mix_socks5_transport {
($a:expr, $b:expr, $proxies:expr) => {
if transports.contains(&$a.to_string()) &&
mixed_transports.contains(&$b.to_string())
{
let mut a_to_b = self.fetch_with_schemes(index, &[$b.to_string()], None);
for (addr, last_seen) in a_to_b.iter_mut() {
for proxy in $proxies {
if let Some(mut endpoint) = proxy {
endpoint.set_path(&format!(
"{}:{}",
addr.host().unwrap(),
addr.port().unwrap()
));
endpoint.set_scheme($a).unwrap();
hosts.push((endpoint, last_seen.clone()));
}
}
}
}
};
}
mix_transport!("tor", "tcp");
mix_transport!("tor+tls", "tcp+tls");
mix_transport!("nym", "tcp");
mix_transport!("nym+tls", "tcp+tls");
mix_socks5_transport!(
"socks5",
"tcp",
[tor_socks5_proxy.clone(), nym_socks5_proxy.clone()]
);
mix_socks5_transport!(
"socks5+tls",
"tcp+tls",
[tor_socks5_proxy.clone(), nym_socks5_proxy.clone()]
);
mix_socks5_transport!("socks5", "tor", [tor_socks5_proxy.clone()]);
mix_socks5_transport!("socks5+tls", "tor+tls", [tor_socks5_proxy.clone()]);
// Filter out a transport from requested transport if we set it to be mixed as
// we don't want to connect directly to that host
let transports: Vec<String> =
transports.iter().filter(|tp| !mixed_transports.contains(tp)).cloned().collect();
// And now the actual requested transports
for (addr, last_seen) in self.fetch_with_schemes(index, &transports, None) {
hosts.push((addr, last_seen));
}
trace!(target: "net::hosts::fetch_addrs()", "Grabbed hosts, length: {}", hosts.len());
hosts
}
/// Get up to limit peers that match the given transport schemes from
/// a hostlist. If limit was not provided, return all matching peers.
fn fetch_with_schemes(
pub(in crate::net) fn fetch_with_schemes(
&self,
color: usize,
schemes: &[String],
@@ -885,6 +798,71 @@ impl HostContainer {
Ok(())
}
/// Performs transport mixing for an url returning a list of addresses
/// with mixed transports.
/// For example we're allowed to use tor:// to connect to tcp:// and tor+tls://
/// to connect to tcp+tls:// or socks5:// to connect to tor://.
/// However, **do not** mix tor:// and tcp+tls://, nor tor+tls:// and tcp://.
pub(in crate::net) fn mix_host(
addr: Url,
transports: &[String],
mixed_transports: &[String],
tor_socks5_proxy: Option<Url>,
nym_socks5_proxy: Option<Url>,
) -> Vec<Url> {
let mut hosts = vec![];
if !mixed_transports.contains(&addr.scheme().to_string()) {
return hosts;
}
macro_rules! mix_transport {
($a:expr, $b:expr) => {
if transports.contains(&$a.to_string()) && addr.scheme() == $b {
let mut addr = addr.clone();
addr.set_scheme($a).unwrap();
hosts.push(addr.clone());
}
};
}
macro_rules! mix_socks5_transport {
($a:expr, $b:expr, $proxies:expr) => {
if transports.contains(&$a.to_string()) && addr.scheme() == $b {
for proxy in $proxies {
if let Some(mut endpoint) = proxy {
endpoint.set_path(&format!(
"{}:{}",
addr.host().unwrap(),
addr.port().unwrap()
));
endpoint.set_scheme($a).unwrap();
hosts.push(endpoint);
}
}
}
};
}
mix_transport!("tor", "tcp");
mix_transport!("tor+tls", "tcp+tls");
mix_transport!("nym", "tcp");
mix_transport!("nym+tls", "tcp+tls");
mix_socks5_transport!(
"socks5",
"tcp",
[tor_socks5_proxy.clone(), nym_socks5_proxy.clone()]
);
mix_socks5_transport!(
"socks5+tls",
"tcp+tls",
[tor_socks5_proxy.clone(), nym_socks5_proxy.clone()]
);
mix_socks5_transport!("socks5", "tor", [tor_socks5_proxy.clone()]);
mix_socks5_transport!("socks5+tls", "tor+tls", [tor_socks5_proxy.clone()]);
hosts
}
}
/// Main parent class for the management and manipulation of
@@ -1930,42 +1908,29 @@ mod tests {
// Test tcp endpoint is changed to tor and tcp will not be used to
// connect to any host directly
#[test]
fn test_transport_tor_mixed_with_tcp_fetch() {
let host_container = HostContainer::new();
host_container.store_or_update(
HostColor::Grey,
fn test_transport_tor_mixed_with_tcp() {
let mixed_hosts = HostContainer::mix_host(
Url::parse("tcp://dark.fi:28880").unwrap(),
0,
);
let fetched_hosts = host_container.fetch(
HostColor::Grey,
&["tor+tls".to_string(), "tcp".to_string(), "tor".to_string()],
&["tcp".to_string()],
Url::parse("socks5://127.0.0.1:9050").ok(),
None,
);
assert_eq!(fetched_hosts.len(), 1);
assert_eq!(fetched_hosts[0].0.to_string(), "tor://dark.fi:28880/");
assert_eq!(mixed_hosts.len(), 1);
assert_eq!(mixed_hosts[0].to_string(), "tor://dark.fi:28880/");
}
// Test when both tor_socks5_proxy and nym_socks5_proxy are passed
// tcp+tls endpoint is changed to socks5+tls and the endpoint is changed to two
// endpoints where one is routed through tor and another through nym
#[test]
fn test_transport_socks5_mixed_with_tcp_through_tor_and_nym_proxy_fetch() {
let host_container = HostContainer::new();
host_container.store_or_update(
HostColor::Grey,
Url::parse("tcp+tls://dark.fi:28880").unwrap(),
0,
);
fn test_transport_socks5_mixed_with_tcp_through_tor_and_nym_proxy() {
let tor_socks5_proxy_url = Url::parse("socks5://127.0.0.1:9050").ok();
let nym_socks5_proxy_url = Url::parse("socks5://127.0.0.1:1080").ok();
let fetched_hosts = host_container.fetch(
HostColor::Grey,
let fetched_hosts = HostContainer::mix_host(
Url::parse("tcp+tls://dark.fi:28880").unwrap(),
&["socks5".to_string(), "socks5+tls".to_string()],
&["tcp+tls".to_string()],
tor_socks5_proxy_url.clone(),
@@ -1974,20 +1939,19 @@ mod tests {
assert_eq!(fetched_hosts.len(), 2);
assert!(
fetched_hosts[0].0.scheme() == "socks5+tls" &&
fetched_hosts[1].0.scheme() == "socks5+tls"
fetched_hosts[0].scheme() == "socks5+tls" && fetched_hosts[1].scheme() == "socks5+tls"
);
assert_eq!(
fetched_hosts
.iter()
.filter(|h| h.0.port() == tor_socks5_proxy_url.as_ref().unwrap().port())
.filter(|h| h.port() == tor_socks5_proxy_url.as_ref().unwrap().port())
.count(),
1
);
assert_eq!(
fetched_hosts
.iter()
.filter(|h| h.0.port() == nym_socks5_proxy_url.as_ref().unwrap().port())
.filter(|h| h.port() == nym_socks5_proxy_url.as_ref().unwrap().port())
.count(),
1
);
@@ -1996,19 +1960,13 @@ mod tests {
// Test tor endpoint is changed to socks5 and tor will not be used to
// connect to any host directly and tor endpoints are not routed through nym
#[test]
fn test_transport_socks5_mixed_with_tor_fetch() {
let host_container = HostContainer::new();
fn test_transport_socks5_mixed_with_tor() {
let addr = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:23330";
host_container.store_or_update(
HostColor::Grey,
Url::parse(&format!("tor://{addr}")).unwrap(),
0,
);
let tor_socks5_proxy_url = Url::parse("socks5://127.0.0.1:9050").ok();
let nym_socks5_proxy_url = Url::parse("socks5://127.0.0.1:1080").ok();
let fetched_hosts = host_container.fetch(
HostColor::Grey,
let fetched_hosts = HostContainer::mix_host(
Url::parse(&format!("tor://{addr}")).unwrap(),
&["socks5".to_string(), "socks5+tls".to_string(), "tor".to_string()],
&["tor".to_string()],
tor_socks5_proxy_url.clone(),
@@ -2016,7 +1974,7 @@ mod tests {
);
assert_eq!(fetched_hosts.len(), 1);
let mixed_url = fetched_hosts[0].0.clone();
let mixed_url = fetched_hosts[0].clone();
assert_eq!(mixed_url.scheme(), tor_socks5_proxy_url.as_ref().unwrap().scheme());
assert_eq!(mixed_url.host(), tor_socks5_proxy_url.as_ref().unwrap().host());
assert_eq!(mixed_url.port(), tor_socks5_proxy_url.as_ref().unwrap().port());
@@ -2025,16 +1983,9 @@ mod tests {
// Test the tcp endpoint is changed to two endpoints socks5 and tor.
#[test]
fn test_transport_tor_and_socks5_mixed_with_tcp_fetch() {
let host_container = HostContainer::new();
host_container.store_or_update(
HostColor::Grey,
fn test_transport_tor_and_socks5_mixed_with_tcp() {
let fetched_hosts = HostContainer::mix_host(
Url::parse("tcp://dark.fi:28880").unwrap(),
0,
);
let fetched_hosts = host_container.fetch(
HostColor::Grey,
&[
"tor".to_string(),
"tor+tls".to_string(),
@@ -2047,7 +1998,7 @@ mod tests {
);
assert_eq!(fetched_hosts.len(), 2);
let endpoints: Vec<_> = fetched_hosts.iter().map(|item| item.0.scheme()).collect();
let endpoints: Vec<_> = fetched_hosts.iter().map(|item| item.scheme()).collect();
assert!(endpoints.iter().all(|&scheme| scheme == "tor" || scheme == "socks5"));
}
}

View File

@@ -216,10 +216,7 @@ impl Slot {
let gold_count = settings.gold_connect_count;
let transports = settings.allowed_transports.clone();
let mixed_transports = settings.mixed_transports.clone();
let preference_strict = settings.slot_preference_strict;
let tor_socks5_proxy = settings.tor_socks5_proxy.clone();
let nym_socks5_proxy = settings.nym_socks5_proxy.clone();
// Drop Settings read lock
drop(settings);
@@ -231,37 +228,13 @@ impl Slot {
// If we only have grey entries, select from the greylist. Otherwise,
// use the preference defined in settings.
let addrs = if grey_only && !preference_strict {
container.fetch(
HostColor::Grey,
&transports,
&mixed_transports,
tor_socks5_proxy,
nym_socks5_proxy,
)
container.fetch_with_schemes(HostColor::Grey as usize, &transports, None)
} else if slot < gold_count {
container.fetch(
HostColor::Gold,
&transports,
&mixed_transports,
tor_socks5_proxy,
nym_socks5_proxy,
)
container.fetch_with_schemes(HostColor::Gold as usize, &transports, None)
} else if slot < white_count {
container.fetch(
HostColor::White,
&transports,
&mixed_transports,
tor_socks5_proxy,
nym_socks5_proxy,
)
container.fetch_with_schemes(HostColor::White as usize, &transports, None)
} else {
container.fetch(
HostColor::Grey,
&transports,
&mixed_transports,
tor_socks5_proxy,
nym_socks5_proxy,
)
container.fetch_with_schemes(HostColor::Grey as usize, &transports, None)
};
hosts.check_addrs(addrs).await