Răsfoiți Sursa

net: Request transport-mixable peers

x 2 săptămâni în urmă
părinte
comite
7217306089

+ 51 - 0
src/net/hosts.rs

@@ -69,6 +69,11 @@ use crate::{
     Error, Result,
 };
 
+/// Canonical transport schemes that may be exchanged through peer discovery.
+/// Proxy endpoint schemes such as SOCKS5 are intentionally excluded.
+pub const SHAREABLE_SCHEMES: [&str; 9] =
+    ["tor", "tls", "tcp", "nym", "i2p", "tor+tls", "nym+tls", "tcp+tls", "i2p+tls"];
+
 pub const LOCAL_HOST_STRS: [&str; 2] = ["localhost", "localhost.localdomain"];
 
 const WHITELIST_MAX_LEN: usize = 5000;
@@ -618,6 +623,26 @@ impl HostContainer {
         schemes
     }
 
+    /// Return deduplicated canonical schemes suitable for peer discovery.
+    pub fn shareable_schemes(
+        transports: &[String],
+        mixed_transports: &[String],
+        tor_socks5_proxy: &Option<Url>,
+        nym_socks5_proxy: &Option<Url>,
+    ) -> Vec<String> {
+        let mut schemes = vec![];
+
+        for scheme in
+            Self::dialable_schemes(transports, mixed_transports, tor_socks5_proxy, nym_socks5_proxy)
+        {
+            if SHAREABLE_SCHEMES.contains(&scheme.as_str()) && !schemes.contains(&scheme) {
+                schemes.push(scheme);
+            }
+        }
+
+        schemes
+    }
+
     /// Perform transport mixing for a URL, returning alternative connection addresses.
     pub fn mix_host(
         addr: &Url,
@@ -1651,6 +1676,32 @@ mod tests {
         assert_eq!(container.fetch_random_with_schemes(HostColor::Grey, &schemes), Some((addr, 1)));
     }
 
+    #[test]
+    fn test_shareable_schemes_include_mixing_sources_once() {
+        let schemes = HostContainer::shareable_schemes(
+            &["tor+tls".to_string(), "tor+tls".to_string()],
+            &["tcp+tls".to_string(), "tcp+tls".to_string()],
+            &None,
+            &None,
+        );
+
+        assert_eq!(schemes, ["tor+tls", "tcp+tls"]);
+    }
+
+    #[test]
+    fn test_shareable_schemes_exclude_proxy_endpoints() {
+        let proxy = Url::parse("socks5://127.0.0.1:9050").ok();
+        let mixed = ["tor", "tcp", "tor+tls", "tcp+tls"].map(str::to_string);
+        let schemes = HostContainer::shareable_schemes(
+            &["socks5".to_string(), "socks5+tls".to_string()],
+            &mixed,
+            &proxy,
+            &None,
+        );
+
+        assert_eq!(schemes, ["tor", "tcp", "tor+tls", "tcp+tls"]);
+    }
+
     #[test]
     fn test_mixed_host_keeps_canonical_url_through_lifecycle() {
         smol::block_on(async {

+ 86 - 76
src/net/protocol/protocol_address.rs

@@ -25,7 +25,7 @@ use url::Url;
 use super::{
     super::{
         channel::ChannelPtr,
-        hosts::{HostColor, HostsPtr},
+        hosts::{HostColor, HostContainer, HostsPtr, SHAREABLE_SCHEMES},
         message::{AddrsMessage, GetAddrsMessage},
         message_publisher::MessageSubscription,
         p2p::P2pPtr,
@@ -70,13 +70,6 @@ pub struct ProtocolAddress {
 
 const PROTO_NAME: &str = "ProtocolAddress";
 
-/// A vector of all currently accepted transports and valid transport
-/// combinations.  Should be updated if and when new transports are
-/// added. Creates a upper bound on the number of transports a given peer
-/// can request.
-const TRANSPORT_COMBOS: [&str; 9] =
-    ["tor", "tls", "tcp", "nym", "i2p", "tor+tls", "nym+tls", "tcp+tls", "i2p+tls"];
-
 /// Strip query parameters from a URL before broadcasting.
 ///
 /// This prevents leaking internal tracking identifiers (e.g., UPnP cookies)
@@ -87,6 +80,53 @@ fn strip_query_params(url: &Url) -> Url {
     stripped
 }
 
+fn select_addrs(container: &HostContainer, request: &GetAddrsMessage) -> Vec<(Url, u64)> {
+    // Ignore private or unknown endpoint schemes and collapse duplicate preferences.
+    let mut requested_transports = vec![];
+    for transport in &request.transports {
+        if SHAREABLE_SCHEMES.contains(&transport.as_str()) &&
+            !requested_transports.contains(transport)
+        {
+            requested_transports.push(transport.clone());
+        }
+    }
+
+    let max = request.max as usize;
+    let response_max = max.saturating_mul(2);
+
+    // Prefer proven and recently refined peers matching the request.
+    let mut addrs =
+        container.fetch_n_random_with_schemes(HostColor::Gold, &requested_transports, max);
+    addrs.append(&mut container.fetch_n_random_with_schemes(
+        HostColor::White,
+        &requested_transports,
+        max,
+    ));
+
+    // Fill the second half with other public peers so uncommon transports
+    // continue to propagate across the network.
+    let remain = response_max.saturating_sub(addrs.len());
+    addrs.append(&mut container.fetch_n_random_excluding_schemes(
+        HostColor::Gold,
+        &requested_transports,
+        remain,
+    ));
+
+    let remain = response_max.saturating_sub(addrs.len());
+    addrs.append(&mut container.fetch_n_random_excluding_schemes(
+        HostColor::White,
+        &requested_transports,
+        remain,
+    ));
+
+    let remain = response_max.saturating_sub(addrs.len());
+    addrs.append(&mut container.fetch_n_random(HostColor::Dark, remain));
+
+    // Dark entries are untrusted and can contain private endpoint schemes.
+    addrs.retain(|addr| SHAREABLE_SCHEMES.contains(&addr.0.scheme()));
+    addrs
+}
+
 impl ProtocolAddress {
     /// Creates a new address protocol. Makes an address, an external address
     /// and a get-address subscription and adds them to the address protocol
@@ -152,69 +192,7 @@ impl ProtocolAddress {
                 "Received GetAddrs({}) message from {}", get_addrs_msg.max, self.channel.display_address(),
             );
 
-            // Filter out transports not meant to be shared like Socks5 and Socks5+tls
-            let requested_transports: Vec<String> = get_addrs_msg
-                .transports
-                .iter()
-                .filter(|tp| TRANSPORT_COMBOS.contains(&tp.as_str()))
-                .cloned()
-                .collect();
-
-            // First we grab address with the requested transports from the gold list
-            debug!(target: "net::protocol_address::handle_receive_get_addrs",
-            "Fetching gold entries with schemes");
-            let mut addrs = self.hosts.container.fetch_n_random_with_schemes(
-                HostColor::Gold,
-                &requested_transports,
-                get_addrs_msg.max as usize,
-            );
-
-            // Then we grab address with the requested transports from the whitelist
-            debug!(target: "net::protocol_address::handle_receive_get_addrs",
-            "Fetching whitelist entries with schemes");
-            addrs.append(&mut self.hosts.container.fetch_n_random_with_schemes(
-                HostColor::White,
-                &requested_transports,
-                get_addrs_msg.max as usize,
-            ));
-
-            // Next we grab addresses without the requested transports
-            // to fill a 2 * max length vector.
-
-            // Then we grab address without the requested transports from the gold list
-            debug!(target: "net::protocol_address::handle_receive_get_addrs",
-            "Fetching gold entries without schemes");
-            let remain = 2 * get_addrs_msg.max as usize - addrs.len();
-            addrs.append(&mut self.hosts.container.fetch_n_random_excluding_schemes(
-                HostColor::Gold,
-                &requested_transports,
-                remain,
-            ));
-
-            // Then we grab address without the requested transports from the white list
-            debug!(target: "net::protocol_address::handle_receive_get_addrs",
-            "Fetching white entries without schemes");
-            let remain = 2 * get_addrs_msg.max as usize - addrs.len();
-            addrs.append(&mut self.hosts.container.fetch_n_random_excluding_schemes(
-                HostColor::White,
-                &requested_transports,
-                remain,
-            ));
-
-            // If there's still space available, take from the Dark list.
-
-            /* NOTE: We share peers from our Dark list because to ensure
-            that non-compatiable transports are shared with other nodes
-            so that they propagate on the network even if they're not
-            popular transports. */
-
-            debug!(target: "net::protocol_address::handle_receive_get_addrs",
-            "Fetching dark entries");
-            let remain = 2 * get_addrs_msg.max as usize - addrs.len();
-            addrs.append(&mut self.hosts.container.fetch_n_random(HostColor::Dark, remain));
-
-            // Filter out transports not meant to be shared like Socks5 and Socks5+tls
-            addrs.retain(|addr| TRANSPORT_COMBOS.contains(&addr.0.scheme()));
+            let addrs = select_addrs(&self.hosts.container, &get_addrs_msg);
 
             debug!(
                 target: "net::protocol_address::handle_receive_get_addrs",
@@ -291,7 +269,12 @@ impl ProtocolBase for ProtocolAddress {
 
         let settings = self.settings.read().await;
         let outbound_connections = settings.outbound_connections;
-        let active_profiles = settings.active_profiles.clone();
+        let transports = HostContainer::shareable_schemes(
+            &settings.active_profiles,
+            &settings.mixed_profiles,
+            &settings.tor_socks5_proxy,
+            &settings.nym_socks5_proxy,
+        );
         let getaddrs_max = settings.getaddrs_max;
         drop(settings);
 
@@ -307,7 +290,7 @@ impl ProtocolBase for ProtocolAddress {
         // We ask for a maximum of u8::MAX addresses from a single node
         let get_addrs = GetAddrsMessage {
             max: getaddrs_max.unwrap_or(outbound_connections.min(u32::MAX as usize) as u32),
-            transports: active_profiles,
+            transports,
         };
         self.channel.send(&get_addrs).await?;
 
@@ -326,19 +309,46 @@ impl ProtocolBase for ProtocolAddress {
 #[cfg(test)]
 mod tests {
     use darkfi_serial::serialize;
+    use smol::lock::RwLock as AsyncRwLock;
+    use std::sync::Arc;
+    use url::Url;
 
-    use crate::net::message::GET_ADDRS_MAX_BYTES;
+    use crate::net::{
+        hosts::{HostColor, HostContainer, Hosts, SHAREABLE_SCHEMES},
+        message::GET_ADDRS_MAX_BYTES,
+        Settings,
+    };
 
-    use super::{GetAddrsMessage, TRANSPORT_COMBOS};
+    use super::{select_addrs, GetAddrsMessage};
 
     // Helps to check if the MAX_BYTES for GetAddrs message is valid as new transports are added
     #[test]
     fn test_get_addrs_msg_size() {
         let message = GetAddrsMessage {
             max: u8::MAX as u32,
-            transports: TRANSPORT_COMBOS.iter().map(|x| x.to_string()).collect(),
+            transports: SHAREABLE_SCHEMES.iter().map(|x| x.to_string()).collect(),
         };
 
         assert_eq!(serialize(&message).len() as u64, GET_ADDRS_MAX_BYTES);
     }
+
+    #[test]
+    fn test_get_addrs_prefers_transport_mixing_source() {
+        let hosts = Hosts::new(Arc::new(AsyncRwLock::new(Settings::default())));
+        let container = &hosts.container;
+        let mixed = Url::parse("tcp+tls://mixed.example:28880").unwrap();
+        let fallback = Url::parse("tcp://fallback.example:28880").unwrap();
+        container.store(HostColor::Gold, mixed.clone(), 2);
+        container.store(HostColor::Gold, fallback.clone(), 1);
+
+        let transports = HostContainer::shareable_schemes(
+            &["tor+tls".to_string()],
+            &["tcp+tls".to_string()],
+            &None,
+            &None,
+        );
+        let response = select_addrs(container, &GetAddrsMessage { max: 1, transports });
+
+        assert_eq!(response, [(mixed, 2), (fallback, 1)]);
+    }
 }

+ 8 - 3
src/net/protocol/protocol_seed.rs

@@ -24,7 +24,7 @@ use tracing::debug;
 use super::{
     super::{
         channel::ChannelPtr,
-        hosts::{HostColor, HostsPtr},
+        hosts::{HostColor, HostContainer, HostsPtr},
         message::{AddrsMessage, GetAddrsMessage},
         message_publisher::MessageSubscription,
         p2p::P2pPtr,
@@ -111,14 +111,19 @@ impl ProtocolBase for ProtocolSeed {
         let settings = self.settings.read().await;
         let outbound_connections = settings.outbound_connections;
         let getaddrs_max = settings.getaddrs_max;
-        let active_profiles = settings.active_profiles.clone();
+        let transports = HostContainer::shareable_schemes(
+            &settings.active_profiles,
+            &settings.mixed_profiles,
+            &settings.tor_socks5_proxy,
+            &settings.nym_socks5_proxy,
+        );
         drop(settings);
 
         // Send get address message
         // We ask for a maximum of u8::MAX addresses from a single node
         let get_addr = GetAddrsMessage {
             max: getaddrs_max.unwrap_or(outbound_connections.min(u32::MAX as usize) as u32),
-            transports: active_profiles,
+            transports,
         };
         self.channel.send(&get_addr).await?;
 

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

@@ -547,13 +547,18 @@ impl PeerDiscovery {
             let outbound_peer_discovery_attempt_time =
                 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 transports = HostContainer::shareable_schemes(
+                &settings.active_profiles,
+                &settings.mixed_profiles,
+                &settings.tor_socks5_proxy,
+                &settings.nym_socks5_proxy,
+            );
             let seeds = settings.seeds.clone();
             drop(settings);
 
@@ -611,8 +616,7 @@ impl PeerDiscovery {
                     state: "getaddr",
                 });
 
-                let get_addrs =
-                    GetAddrsMessage { max: getaddrs_max.unwrap_or(1), transports: active_profiles };
+                let get_addrs = GetAddrsMessage { max: getaddrs_max.unwrap_or(1), transports };
 
                 if let Err(e) = self.p2p().broadcast(&get_addrs).await {
                     debug!(

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

@@ -622,7 +622,12 @@ impl PeerDiscoveryBase for PeerDiscovery {
                 settings.outbound_peer_discovery_attempt_time;
             let outbound_connections = settings.outbound_connections;
             let getaddrs_max = settings.getaddrs_max;
-            let active_profiles = settings.active_profiles.clone();
+            let transports = HostContainer::shareable_schemes(
+                &settings.active_profiles,
+                &settings.mixed_profiles,
+                &settings.tor_socks5_proxy,
+                &settings.nym_socks5_proxy,
+            );
             let seeds = settings.seeds.clone();
             drop(settings);
 
@@ -663,7 +668,7 @@ impl PeerDiscoveryBase for PeerDiscovery {
 
                 let get_addrs = GetAddrsMessage {
                     max: getaddrs_max.unwrap_or(outbound_connections.min(u32::MAX as usize) as u32),
-                    transports: active_profiles,
+                    transports,
                 };
 
                 if let Err(e) = self.p2p().broadcast(&get_addrs).await {