From ec59184e8571cc39b1be844cfc570a918ff57ccc Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 30 Jun 2021 18:11:34 +1000 Subject: [PATCH] Refactor `fmt_as_address_string` to return a structured value Instead of formatting to a string right away, we parse the multiaddress into a stricter data structure that only allows the kind of addresses we can dial through Tor. This will allow us to perform further checks on the parsed address. --- swap/src/network/tor_transport.rs | 139 +++++++++++++++++++----------- 1 file changed, 88 insertions(+), 51 deletions(-) diff --git a/swap/src/network/tor_transport.rs b/swap/src/network/tor_transport.rs index 0136ba73..13a71420 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,15 +35,17 @@ impl Transport for TorDialOnlyTransport { } fn dial(self, addr: Multiaddr) -> Result> { - let tor_address_string = fmt_as_address_string(addr.clone())?; + let tor_compatible_address = TorCompatibleAddress::from_multiaddr(Cow::Borrowed(&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) - .await - .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?; + let stream = Socks5Stream::connect( + (Ipv4Addr::LOCALHOST, self.socks_port), + tor_compatible_address.to_string(), + ) + .await + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?; tracing::trace!("Connection through Tor established"); @@ -57,36 +60,56 @@ 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(); +/// 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 }, +} - 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() - )) +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())), } - // 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)] @@ -98,8 +121,10 @@ pub mod test { 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_string = + TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())) + .unwrap() + .to_string(); assert_eq!( address_string, "oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024" @@ -109,55 +134,67 @@ pub mod test { #[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. "); + let address_string = + TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())) + .unwrap() + .to_string(); assert_eq!(address_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. "); + let address_string = + TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())) + .unwrap() + .to_string(); assert_eq!(address_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. "); + let address_string = + TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())) + .unwrap() + .to_string(); assert_eq!(address_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. "); + let address_string = + TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())) + .unwrap() + .to_string(); assert_eq!(address_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. "); + let address_string = + TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())) + .unwrap() + .to_string(); assert_eq!(address_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. "); + let address_string = + TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())) + .unwrap() + .to_string(); assert_eq!(address_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(); } }