Преглед изворни кода

net: Select transport-mixed peers

x пре 2 недеља
родитељ
комит
91c961d916

+ 140 - 0
src/net/hosts.rs

@@ -585,6 +585,39 @@ impl HostContainer {
         vec![]
     }
 
+    /// Return canonical address schemes that can be dialed directly or through
+    /// a configured mixed route.
+    pub fn dialable_schemes(
+        transports: &[String],
+        mixed_transports: &[String],
+        tor_socks5_proxy: &Option<Url>,
+        nym_socks5_proxy: &Option<Url>,
+    ) -> Vec<String> {
+        let mut schemes = transports.to_vec();
+
+        for scheme in mixed_transports {
+            if schemes.contains(scheme) {
+                continue
+            }
+
+            let Ok(addr) = Url::parse(&format!("{scheme}://example.com:1")) else { continue };
+
+            if !Self::mix_host(
+                &addr,
+                transports,
+                mixed_transports,
+                tor_socks5_proxy,
+                nym_socks5_proxy,
+            )
+            .is_empty()
+            {
+                schemes.push(scheme.clone());
+            }
+        }
+
+        schemes
+    }
+
     /// Perform transport mixing for a URL, returning alternative connection addresses.
     pub fn mix_host(
         addr: &Url,
@@ -1560,6 +1593,113 @@ mod tests {
         assert!(endpoints.is_empty());
     }
 
+    #[test]
+    fn test_dialable_schemes_include_valid_mixing_sources() {
+        let schemes = HostContainer::dialable_schemes(
+            &["tor".to_string(), "tor+tls".to_string()],
+            &["tcp".to_string(), "tcp+tls".to_string()],
+            &None,
+            &None,
+        );
+
+        assert_eq!(schemes, ["tor", "tor+tls", "tcp", "tcp+tls"]);
+    }
+
+    #[test]
+    fn test_dialable_schemes_exclude_incompatible_mixing_sources() {
+        let schemes = HostContainer::dialable_schemes(
+            &["tor".to_string()],
+            &["tcp+tls".to_string()],
+            &None,
+            &None,
+        );
+
+        assert_eq!(schemes, ["tor"]);
+    }
+
+    #[test]
+    fn test_dialable_schemes_require_configured_proxy() {
+        let without_proxy = HostContainer::dialable_schemes(
+            &["socks5+tls".to_string()],
+            &["tcp+tls".to_string()],
+            &None,
+            &None,
+        );
+        let with_proxy = HostContainer::dialable_schemes(
+            &["socks5+tls".to_string()],
+            &["tcp+tls".to_string()],
+            &None,
+            &Url::parse("socks5://127.0.0.1:1080").ok(),
+        );
+
+        assert_eq!(without_proxy, ["socks5+tls"]);
+        assert_eq!(with_proxy, ["socks5+tls", "tcp+tls"]);
+    }
+
+    #[test]
+    fn test_dialable_schemes_select_mixed_host() {
+        let container = HostContainer::new();
+        let addr = Url::parse("tcp+tls://dark.fi:28880").unwrap();
+        container.store(HostColor::Grey, addr.clone(), 1);
+        let schemes = HostContainer::dialable_schemes(
+            &["tor+tls".to_string()],
+            &["tcp+tls".to_string()],
+            &None,
+            &None,
+        );
+
+        assert_eq!(container.fetch_random_with_schemes(HostColor::Grey, &schemes), Some((addr, 1)));
+    }
+
+    #[test]
+    fn test_mixed_host_keeps_canonical_url_through_lifecycle() {
+        smol::block_on(async {
+            let settings = Settings {
+                active_profiles: vec!["tor+tls".to_string()],
+                mixed_profiles: vec!["tcp+tls".to_string()],
+                ..Default::default()
+            };
+            let hosts = make_hosts_with_settings(settings.clone());
+            let canonical = Url::parse("tcp+tls://dark.fi:28880").unwrap();
+            let derived = Url::parse("tor+tls://dark.fi:28880").unwrap();
+
+            hosts.insert(HostColor::Grey, &[(canonical.clone(), 1)]).await;
+
+            let schemes = HostContainer::dialable_schemes(
+                &settings.active_profiles,
+                &settings.mixed_profiles,
+                &settings.tor_socks5_proxy,
+                &settings.nym_socks5_proxy,
+            );
+            let selected = hosts
+                .container
+                .fetch_random_with_schemes(HostColor::Grey, &schemes)
+                .expect("mixed peer should be selectable");
+            assert_eq!(selected, (canonical.clone(), 1));
+
+            hosts.try_register(canonical.clone(), HostState::Connect).unwrap();
+            assert_eq!(
+                HostContainer::resolve_dial_endpoints(
+                    &selected.0,
+                    &settings.active_profiles,
+                    &settings.mixed_profiles,
+                    &settings.tor_socks5_proxy,
+                    &settings.nym_socks5_proxy,
+                ),
+                vec![(derived.clone(), true)]
+            );
+
+            hosts.move_host(&canonical, 2, HostColor::Gold).await.unwrap();
+            assert!(hosts.container.contains(HostColor::Gold, &canonical));
+            assert!(!hosts.container.contains(HostColor::Gold, &derived));
+
+            hosts.unregister(&canonical).unwrap();
+            hosts.move_host(&canonical, 2, HostColor::Grey).await.unwrap();
+            assert!(hosts.container.contains(HostColor::Grey, &canonical));
+            assert!(!hosts.container.contains(HostColor::Grey, &derived));
+        });
+    }
+
     #[test]
     fn test_filter_classifies_active_host_as_grey() {
         smol::block_on(async {

+ 23 - 7
src/net/session/direct_session.rs

@@ -42,7 +42,7 @@ use super::{
     super::{
         connector::Connector,
         dnet::{self, dnetev, DnetEvent},
-        hosts::{HostColor, HostState},
+        hosts::{HostColor, HostContainer, HostState},
         message::GetAddrsMessage,
         p2p::{P2p, P2pPtr},
     },
@@ -274,7 +274,13 @@ impl DirectSession {
 
         let settings = self.p2p().settings().read_arc().await;
         let seeds = settings.seeds.clone();
-        let active_profiles = settings.active_profiles.clone();
+        let dial_endpoints = HostContainer::resolve_dial_endpoints(
+            &addr,
+            &settings.active_profiles,
+            &settings.mixed_profiles,
+            &settings.tor_socks5_proxy,
+            &settings.nym_socks5_proxy,
+        );
         drop(settings);
 
         // Do not establish a connection to a host that is also configured as a seed.
@@ -300,13 +306,17 @@ impl DirectSession {
             )))
         }
 
-        // Abort if we do not support this transport.
-        if !active_profiles.contains(&addr.scheme().to_string()) {
+        // Abort if we cannot dial this transport directly or through mixing.
+        if dial_endpoints.is_empty() {
             return Err(Error::UnsupportedTransport(addr.scheme().to_string()))
         }
 
-        // Abort if this peer is IPv6 and we do not support it.
-        if !hosts.ipv6_available.load(Ordering::SeqCst) && hosts.is_ipv6(&addr) {
+        // Direct IPv6 requires local IPv6 connectivity. A mixed route resolves
+        // the destination through its transport and does not have this requirement.
+        if !hosts.ipv6_available.load(Ordering::SeqCst) &&
+            hosts.is_ipv6(&addr) &&
+            dial_endpoints.iter().all(|(_, mixed)| !mixed)
+        {
             return Err(Error::ConnectFailed(format!("[{addr}]: IPv6 is unavailable")))
         }
 
@@ -538,6 +548,12 @@ impl PeerDiscovery {
                 settings.outbound_peer_discovery_attempt_time;
             let getaddrs_max = settings.getaddrs_max;
             let active_profiles = settings.active_profiles.clone();
+            let dialable_schemes = HostContainer::dialable_schemes(
+                &settings.active_profiles,
+                &settings.mixed_profiles,
+                &settings.tor_socks5_proxy,
+                &settings.nym_socks5_proxy,
+            );
             let seeds = settings.seeds.clone();
             drop(settings);
 
@@ -573,7 +589,7 @@ impl PeerDiscovery {
                         .p2p()
                         .hosts()
                         .container
-                        .fetch_random_with_schemes(*color, &active_profiles)
+                        .fetch_random_with_schemes(*color, &dialable_schemes)
                     {
                         channel = self.p2p().session_direct().get_channel(&url).await.ok();
                         break;

+ 7 - 2
src/net/session/outbound_session.rs

@@ -45,7 +45,7 @@ use super::{
         channel::ChannelPtr,
         connector::Connector,
         dnet::{self, dnetev, DnetEvent},
-        hosts::{HostColor, HostState},
+        hosts::{HostColor, HostContainer, HostState},
         message::GetAddrsMessage,
         p2p::{P2p, P2pPtr},
     },
@@ -276,7 +276,12 @@ impl Slot {
         // Acquire Settings read lock
         let settings = self.p2p().settings().read_arc().await;
 
-        let transports = settings.active_profiles.clone();
+        let transports = HostContainer::dialable_schemes(
+            &settings.active_profiles,
+            &settings.mixed_profiles,
+            &settings.tor_socks5_proxy,
+            &settings.nym_socks5_proxy,
+        );
         let outbound_connections = settings.outbound_connections;
         let known_peer_percent = settings.known_peer_percent;
         let disable_greys = settings.disable_greys;

+ 8 - 3
src/net/session/refine_session.rs

@@ -44,7 +44,7 @@ use super::super::p2p::{P2p, P2pPtr};
 use crate::{
     net::{
         connector::Connector,
-        hosts::HostColor,
+        hosts::{HostColor, HostContainer},
         protocol::ProtocolVersion,
         session::{Session, SessionBitFlag, SESSION_REFINE},
     },
@@ -234,7 +234,12 @@ impl GreylistRefinery {
             let settings = self.p2p().settings().read_arc().await;
             let greylist_refinery_interval = settings.greylist_refinery_interval;
             let time_with_no_connections = settings.time_with_no_connections;
-            let active_profiles = settings.active_profiles.clone();
+            let dialable_schemes = HostContainer::dialable_schemes(
+                &settings.active_profiles,
+                &settings.mixed_profiles,
+                &settings.tor_socks5_proxy,
+                &settings.nym_socks5_proxy,
+            );
             drop(settings);
 
             sleep(greylist_refinery_interval).await;
@@ -275,7 +280,7 @@ impl GreylistRefinery {
             }
 
             // Only attempt to refine peers that match our transports.
-            match hosts.container.fetch_random_with_schemes(HostColor::Grey, &active_profiles) {
+            match hosts.container.fetch_random_with_schemes(HostColor::Grey, &dialable_schemes) {
                 Some((url, _last_seen)) => {
                     if !hosts.refinable(&url) {
                         debug!(target: "net::refinery", "Unable to refine addr={}", url);