Procházet zdrojové kódy

net: Preserve host blacklist schemes

x před 2 týdny
rodič
revize
08c26e64bf
2 změnil soubory, kde provedl 88 přidání a 6 odebrání
  1. 70 2
      src/net/connector.rs
  2. 18 4
      src/net/hosts.rs

+ 70 - 2
src/net/connector.rs

@@ -32,7 +32,6 @@ use url::Url;
 
 use super::{
     channel::{Channel, ChannelPtr},
-    hosts::HostColor,
     session::SessionWeakPtr,
     settings::Settings,
     transport::Dialer,
@@ -48,6 +47,27 @@ enum DialRoutesError {
     Failed(DialFailures),
 }
 
+fn partition_blacklisted_endpoints<F>(
+    endpoints: Vec<(Url, bool)>,
+    mut is_blacklisted: F,
+) -> (Vec<(Url, bool)>, Vec<Url>)
+where
+    F: FnMut(&Url) -> bool,
+{
+    let mut allowed = vec![];
+    let mut blocked = vec![];
+
+    for (endpoint, mixed_transport) in endpoints {
+        if is_blacklisted(&endpoint) {
+            blocked.push(endpoint);
+        } else {
+            allowed.push((endpoint, mixed_transport));
+        }
+    }
+
+    (allowed, blocked)
+}
+
 fn build_dial_routes(endpoints: Vec<(Url, bool)>, settings: &Settings) -> Vec<DialRoute> {
     endpoints
         .into_iter()
@@ -122,7 +142,8 @@ impl Connector {
     /// Establish an outbound connection
     pub async fn connect(&self, url: &Url) -> Result<(Url, ChannelPtr)> {
         let hosts = self.session.upgrade().unwrap().p2p().hosts();
-        if hosts.container.contains(HostColor::Black, url) || hosts.block_all_ports(url) {
+        // A canonical blacklist match blocks the peer regardless of route.
+        if hosts.is_blacklisted(url) {
             let url = sanitized_url(url);
             verbose!(target: "net::connector::connect", "Peer {url} is blacklisted");
             return Err(Error::ConnectFailed(format!("[{url}]: Peer is blacklisted")));
@@ -143,6 +164,24 @@ impl Connector {
             return Err(Error::UnsupportedTransport(url.scheme().to_string()))
         }
 
+        // A derived endpoint match blocks only that route, allowing a safe
+        // alternative transport to be tried when one is available.
+        let (endpoints, blocked) =
+            partition_blacklisted_endpoints(endpoints, |endpoint| hosts.is_blacklisted(endpoint));
+        for endpoint in blocked {
+            verbose!(
+                target: "net::connector::connect",
+                "Skipping blacklisted connection route {}",
+                sanitized_url(&endpoint),
+            );
+        }
+        if endpoints.is_empty() {
+            return Err(Error::ConnectFailed(format!(
+                "[{}]: All connection routes are blacklisted",
+                sanitized_url(url)
+            )))
+        }
+
         let routes = build_dial_routes(endpoints, &settings);
         drop(settings);
 
@@ -203,6 +242,35 @@ mod tests {
         (Url::parse(url).unwrap(), true, Duration::from_secs(timeout))
     }
 
+    #[test]
+    fn test_mixed_routes_skip_blacklisted_endpoint() {
+        let endpoints = vec![
+            (Url::parse("tor+tls://peer.example:28880").unwrap(), true),
+            (Url::parse("nym+tls://peer.example:28880").unwrap(), true),
+        ];
+
+        let (allowed, blocked) =
+            partition_blacklisted_endpoints(endpoints, |url| url.scheme() == "tor+tls");
+
+        assert_eq!(allowed.len(), 1);
+        assert_eq!(allowed[0].0.scheme(), "nym+tls");
+        assert_eq!(blocked.len(), 1);
+        assert_eq!(blocked[0].scheme(), "tor+tls");
+    }
+
+    #[test]
+    fn test_mixed_routes_reject_all_blacklisted_endpoints() {
+        let endpoints = vec![
+            (Url::parse("tor://peer.example:28880").unwrap(), true),
+            (Url::parse("nym://peer.example:28880").unwrap(), true),
+        ];
+
+        let (allowed, blocked) = partition_blacklisted_endpoints(endpoints, |_| true);
+
+        assert!(allowed.is_empty());
+        assert_eq!(blocked.len(), 2);
+    }
+
     #[test]
     fn test_dial_routes_use_endpoint_profile_timeouts() {
         let mut settings = Settings::default();

+ 18 - 4
src/net/hosts.rs

@@ -1043,7 +1043,7 @@ impl Hosts {
             }
 
             // Skip blacklisted
-            if self.container.contains(HostColor::Black, addr) || self.block_all_ports(addr) {
+            if self.is_blacklisted(addr) {
                 verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: blacklisted");
                 continue;
             }
@@ -1175,9 +1175,14 @@ impl Hosts {
             None => return false,
         };
 
-        self.container.lists.read()[HostColor::Black as usize]
-            .iter()
-            .any(|(u, _)| u.host() == Some(host.clone()) && u.port().is_none())
+        self.container.lists.read()[HostColor::Black as usize].iter().any(|(u, _)| {
+            u.scheme() == url.scheme() && u.host() == Some(host.clone()) && u.port().is_none()
+        })
+    }
+
+    /// Check exact-port and all-port blacklist entries for a URL.
+    pub(crate) fn is_blacklisted(&self, url: &Url) -> bool {
+        self.container.contains(HostColor::Black, url) || self.block_all_ports(url)
     }
 
     pub fn is_local_host(&self, url: &Url) -> bool {
@@ -1481,9 +1486,18 @@ mod tests {
 
         let test_url = Url::parse("tcp+tls://blocked.com:9999").unwrap();
         assert!(hosts.block_all_ports(&test_url));
+        assert!(hosts.is_blacklisted(&test_url));
+
+        for scheme in ["tcp", "tor", "tor+tls"] {
+            let other_scheme = Url::parse(&format!("{scheme}://blocked.com:9999")).unwrap();
+            assert!(!hosts.block_all_ports(&other_scheme));
+            assert!(!hosts.is_blacklisted(&other_scheme));
+        }
 
         let test_url2 = Url::parse("tcp+tls://example.com:9999").unwrap();
         assert!(!hosts.block_all_ports(&test_url2));
+        assert!(!hosts.is_blacklisted(&test_url2));
+        assert!(hosts.is_blacklisted(&with_port));
     }
 
     #[test]