diff --git a/swap/src/network/tor_transport.rs b/swap/src/network/tor_transport.rs index 0136ba73..9c1ad8d1 100644 --- a/swap/src/network/tor_transport.rs +++ b/swap/src/network/tor_transport.rs @@ -6,8 +6,9 @@ use libp2p::core::transport::TransportError; use libp2p::core::Transport; use libp2p::tcp::tokio::{Tcp, TcpStream}; use libp2p::tcp::TcpListenStream; -use std::io; -use std::net::Ipv4Addr; +use std::borrow::Cow; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::{fmt, io}; use tokio_socks::tcp::Socks5Stream; /// A [`Transport`] that can dial onion addresses through a running Tor daemon. @@ -34,13 +35,17 @@ impl Transport for TorDialOnlyTransport { } fn dial(self, addr: Multiaddr) -> Result> { - let tor_address_string = fmt_as_address_string(addr.clone())?; + let address = TorCompatibleAddress::from_multiaddr(Cow::Borrowed(&addr))?; + + if address.is_certainly_not_reachable_via_tor_daemon() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } let dial_future = async move { tracing::trace!("Connecting through Tor proxy to address: {}", addr); let stream = - Socks5Stream::connect((Ipv4Addr::LOCALHOST, self.socks_port), tor_address_string) + Socks5Stream::connect((Ipv4Addr::LOCALHOST, self.socks_port), address.to_string()) .await .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?; @@ -57,36 +62,72 @@ impl Transport for TorDialOnlyTransport { } } -/// Formats the given [`Multiaddr`] as an "address" string. -/// -/// For our purposes, we define an address as {HOST}(.{TLD}):{PORT}. This format -/// is what is compatible with the Tor daemon and allows us to route traffic -/// through Tor. -fn fmt_as_address_string(multi: Multiaddr) -> Result> { - let mut protocols = multi.iter(); - - let address_string = match protocols.next() { - // if it is an Onion address, we have all we need and can return - Some(Protocol::Onion3(addr)) => { - return Ok(format!( - "{}.onion:{}", - BASE32.encode(addr.hash()).to_lowercase(), - addr.port() - )) +/// Represents an address that is _compatible_ with Tor, i.e. can be resolved by +/// the Tor daemon. +#[derive(Debug)] +enum TorCompatibleAddress { + Onion3 { host: String, port: u16 }, + Dns { address: String, port: u16 }, + Ip4 { address: Ipv4Addr, port: u16 }, + Ip6 { address: Ipv6Addr, port: u16 }, +} + +impl TorCompatibleAddress { + /// Constructs a new [`TorCompatibleAddress`] from a [`Multiaddr`]. + fn from_multiaddr(multi: Cow<'_, Multiaddr>) -> Result> { + match multi.iter().collect::>().as_slice() { + [Protocol::Onion3(onion), ..] => Ok(TorCompatibleAddress::Onion3 { + host: BASE32.encode(onion.hash()).to_lowercase(), + port: onion.port(), + }), + [Protocol::Ip4(address), Protocol::Tcp(port) | Protocol::Udp(port), ..] => { + Ok(TorCompatibleAddress::Ip4 { + address: *address, + port: *port, + }) + } + [Protocol::Dns(address) | Protocol::Dns4(address), Protocol::Tcp(port) | Protocol::Udp(port), ..] => { + Ok(TorCompatibleAddress::Dns { + address: format!("{}", address), + port: *port, + }) + } + [Protocol::Ip6(address), Protocol::Tcp(port) | Protocol::Udp(port), ..] => { + Ok(TorCompatibleAddress::Ip6 { + address: *address, + port: *port, + }) + } + _ => Err(TransportError::MultiaddrNotSupported(multi.into_owned())), + } + } + + /// Checks if the address is reachable via the Tor daemon. + /// + /// The Tor daemon can dial onion addresses, resolve DNS names and dial + /// IP4/IP6 addresses reachable via the public Internet. + /// We can't guarantee that an address is reachable via the Internet but we + /// can say that some addresses are almost certainly not reachable, for + /// example, loopback addresses. + fn is_certainly_not_reachable_via_tor_daemon(&self) -> bool { + match self { + TorCompatibleAddress::Onion3 { .. } => false, + TorCompatibleAddress::Dns { address, .. } => address == "localhost", + TorCompatibleAddress::Ip4 { address, .. } => address.is_loopback(), + TorCompatibleAddress::Ip6 { address, .. } => address.is_loopback(), } - // Deal with non-onion addresses - Some(Protocol::Ip4(addr)) => format!("{}", addr), - Some(Protocol::Ip6(addr)) => format!("{}", addr), - Some(Protocol::Dns(addr) | Protocol::Dns4(addr)) => format!("{}", addr), - _ => return Err(TransportError::MultiaddrNotSupported(multi)), - }; - - let port = match protocols.next() { - Some(Protocol::Tcp(port) | Protocol::Udp(port)) => port, - _ => return Err(TransportError::MultiaddrNotSupported(multi)), - }; - - Ok(format!("{}:{}", address_string, port)) + } +} + +impl fmt::Display for TorCompatibleAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TorCompatibleAddress::Onion3 { host, port } => write!(f, "{}.onion:{}", host, port), + TorCompatibleAddress::Dns { address, port } => write!(f, "{}:{}", address, port), + TorCompatibleAddress::Ip4 { address, port } => write!(f, "{}:{}", address, port), + TorCompatibleAddress::Ip6 { address, port } => write!(f, "{}:{}", address, port), + } + } } #[cfg(test)] @@ -95,69 +136,76 @@ pub mod test { #[test] fn test_tor_address_string() { - let address = - "/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9" - ; - let address_string = fmt_as_address_string(address.parse().unwrap()) - .expect("To be a multi formatted address."); + let address = tor_compatible_address_from_str("/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9"); + + assert!(!address.is_certainly_not_reachable_via_tor_daemon()); assert_eq!( - address_string, + address.to_string(), "oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024" ); } #[test] fn tcp_to_address_string_should_be_some() { - let address = "/ip4/127.0.0.1/tcp/7777"; - let address_string = fmt_as_address_string(address.parse().unwrap()) - .expect("To be a formatted multi address. "); - assert_eq!(address_string, "127.0.0.1:7777"); + let address = tor_compatible_address_from_str("/ip4/127.0.0.1/tcp/7777"); + + assert!(address.is_certainly_not_reachable_via_tor_daemon()); + assert_eq!(address.to_string(), "127.0.0.1:7777"); } #[test] fn ip6_to_address_string_should_be_some() { - let address = "/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777"; - let address_string = fmt_as_address_string(address.parse().unwrap()) - .expect("To be a formatted multi address. "); - assert_eq!(address_string, "2001:db8:85a3:8d3:1319:8a2e:370:7348:7777"); + let address = + tor_compatible_address_from_str("/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777"); + + assert!(!address.is_certainly_not_reachable_via_tor_daemon()); + assert_eq!( + address.to_string(), + "2001:db8:85a3:8d3:1319:8a2e:370:7348:7777" + ); } #[test] fn udp_to_address_string_should_be_some() { - let address = "/ip4/127.0.0.1/udp/7777"; - let address_string = fmt_as_address_string(address.parse().unwrap()) - .expect("To be a formatted multi address. "); - assert_eq!(address_string, "127.0.0.1:7777"); + let address = tor_compatible_address_from_str("/ip4/127.0.0.1/udp/7777"); + + assert!(address.is_certainly_not_reachable_via_tor_daemon()); + assert_eq!(address.to_string(), "127.0.0.1:7777"); } #[test] fn ws_to_address_string_should_be_some() { - let address = "/ip4/127.0.0.1/tcp/7777/ws"; - let address_string = fmt_as_address_string(address.parse().unwrap()) - .expect("To be a formatted multi address. "); - assert_eq!(address_string, "127.0.0.1:7777"); + let address = tor_compatible_address_from_str("/ip4/127.0.0.1/tcp/7777/ws"); + + assert!(address.is_certainly_not_reachable_via_tor_daemon()); + assert_eq!(address.to_string(), "127.0.0.1:7777"); } #[test] fn dns4_to_address_string_should_be_some() { - let address = "/dns4/randomdomain.com/tcp/7777"; - let address_string = fmt_as_address_string(address.parse().unwrap()) - .expect("To be a formatted multi address. "); - assert_eq!(address_string, "randomdomain.com:7777"); + let address = tor_compatible_address_from_str("/dns4/randomdomain.com/tcp/7777"); + + assert!(!address.is_certainly_not_reachable_via_tor_daemon()); + assert_eq!(address.to_string(), "randomdomain.com:7777"); } #[test] fn dns_to_address_string_should_be_some() { - let address = "/dns/randomdomain.com/tcp/7777"; - let address_string = fmt_as_address_string(address.parse().unwrap()) - .expect("To be a formatted multi address. "); - assert_eq!(address_string, "randomdomain.com:7777"); + let address = tor_compatible_address_from_str("/dns/randomdomain.com/tcp/7777"); + + assert!(!address.is_certainly_not_reachable_via_tor_daemon()); + assert_eq!(address.to_string(), "randomdomain.com:7777"); } #[test] - fn dnsaddr_to_address_string_should_be_none() { + fn dnsaddr_to_address_string_should_be_error() { let address = "/dnsaddr/randomdomain.com"; - let address_string = fmt_as_address_string(address.parse().unwrap()).ok(); - assert_eq!(address_string, None); + + let _ = + TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())).unwrap_err(); + } + + fn tor_compatible_address_from_str(str: &str) -> TorCompatibleAddress { + TorCompatibleAddress::from_multiaddr(Cow::Owned(str.parse().unwrap())).unwrap() } }