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.
This commit is contained in:
Thomas Eizinger 2021-06-30 18:11:34 +10:00
parent 910ed9a333
commit ec59184e85
No known key found for this signature in database
GPG Key ID: 651AC83A6C6C8B96

View File

@ -6,8 +6,9 @@ use libp2p::core::transport::TransportError;
use libp2p::core::Transport; use libp2p::core::Transport;
use libp2p::tcp::tokio::{Tcp, TcpStream}; use libp2p::tcp::tokio::{Tcp, TcpStream};
use libp2p::tcp::TcpListenStream; use libp2p::tcp::TcpListenStream;
use std::io; use std::borrow::Cow;
use std::net::Ipv4Addr; use std::net::{Ipv4Addr, Ipv6Addr};
use std::{fmt, io};
use tokio_socks::tcp::Socks5Stream; use tokio_socks::tcp::Socks5Stream;
/// A [`Transport`] that can dial onion addresses through a running Tor daemon. /// 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<Self::Dial, TransportError<Self::Error>> { fn dial(self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
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 { let dial_future = async move {
tracing::trace!("Connecting through Tor proxy to address: {}", addr); tracing::trace!("Connecting through Tor proxy to address: {}", addr);
let stream = let stream = Socks5Stream::connect(
Socks5Stream::connect((Ipv4Addr::LOCALHOST, self.socks_port), tor_address_string) (Ipv4Addr::LOCALHOST, self.socks_port),
.await tor_compatible_address.to_string(),
.map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?; )
.await
.map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?;
tracing::trace!("Connection through Tor established"); tracing::trace!("Connection through Tor established");
@ -57,36 +60,56 @@ impl Transport for TorDialOnlyTransport {
} }
} }
/// Formats the given [`Multiaddr`] as an "address" string. /// Represents an address that is _compatible_ with Tor, i.e. can be resolved by
/// /// the Tor daemon.
/// For our purposes, we define an address as {HOST}(.{TLD}):{PORT}. This format #[derive(Debug)]
/// is what is compatible with the Tor daemon and allows us to route traffic enum TorCompatibleAddress {
/// through Tor. Onion3 { host: String, port: u16 },
fn fmt_as_address_string(multi: Multiaddr) -> Result<String, TransportError<io::Error>> { Dns { address: String, port: u16 },
let mut protocols = multi.iter(); Ip4 { address: Ipv4Addr, port: u16 },
Ip6 { address: Ipv6Addr, port: u16 },
}
let address_string = match protocols.next() { impl TorCompatibleAddress {
// if it is an Onion address, we have all we need and can return /// Constructs a new [`TorCompatibleAddress`] from a [`Multiaddr`].
Some(Protocol::Onion3(addr)) => { fn from_multiaddr(multi: Cow<'_, Multiaddr>) -> Result<Self, TransportError<io::Error>> {
return Ok(format!( match multi.iter().collect::<Vec<_>>().as_slice() {
"{}.onion:{}", [Protocol::Onion3(onion), ..] => Ok(TorCompatibleAddress::Onion3 {
BASE32.encode(addr.hash()).to_lowercase(), host: BASE32.encode(onion.hash()).to_lowercase(),
addr.port() 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() { impl fmt::Display for TorCompatibleAddress {
Some(Protocol::Tcp(port) | Protocol::Udp(port)) => port, fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
_ => return Err(TransportError::MultiaddrNotSupported(multi)), match self {
}; TorCompatibleAddress::Onion3 { host, port } => write!(f, "{}.onion:{}", host, port),
TorCompatibleAddress::Dns { address, port } => write!(f, "{}:{}", address, port),
Ok(format!("{}:{}", address_string, port)) TorCompatibleAddress::Ip4 { address, port } => write!(f, "{}:{}", address, port),
TorCompatibleAddress::Ip6 { address, port } => write!(f, "{}:{}", address, port),
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -98,8 +121,10 @@ pub mod test {
let address = let address =
"/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9" "/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9"
; ;
let address_string = fmt_as_address_string(address.parse().unwrap()) let address_string =
.expect("To be a multi formatted address."); TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap()))
.unwrap()
.to_string();
assert_eq!( assert_eq!(
address_string, address_string,
"oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024" "oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024"
@ -109,55 +134,67 @@ pub mod test {
#[test] #[test]
fn tcp_to_address_string_should_be_some() { fn tcp_to_address_string_should_be_some() {
let address = "/ip4/127.0.0.1/tcp/7777"; let address = "/ip4/127.0.0.1/tcp/7777";
let address_string = fmt_as_address_string(address.parse().unwrap()) let address_string =
.expect("To be a formatted multi address. "); TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap()))
.unwrap()
.to_string();
assert_eq!(address_string, "127.0.0.1:7777"); assert_eq!(address_string, "127.0.0.1:7777");
} }
#[test] #[test]
fn ip6_to_address_string_should_be_some() { fn ip6_to_address_string_should_be_some() {
let address = "/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777"; let address = "/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777";
let address_string = fmt_as_address_string(address.parse().unwrap()) let address_string =
.expect("To be a formatted multi address. "); TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap()))
.unwrap()
.to_string();
assert_eq!(address_string, "2001:db8:85a3:8d3:1319:8a2e:370:7348:7777"); assert_eq!(address_string, "2001:db8:85a3:8d3:1319:8a2e:370:7348:7777");
} }
#[test] #[test]
fn udp_to_address_string_should_be_some() { fn udp_to_address_string_should_be_some() {
let address = "/ip4/127.0.0.1/udp/7777"; let address = "/ip4/127.0.0.1/udp/7777";
let address_string = fmt_as_address_string(address.parse().unwrap()) let address_string =
.expect("To be a formatted multi address. "); TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap()))
.unwrap()
.to_string();
assert_eq!(address_string, "127.0.0.1:7777"); assert_eq!(address_string, "127.0.0.1:7777");
} }
#[test] #[test]
fn ws_to_address_string_should_be_some() { fn ws_to_address_string_should_be_some() {
let address = "/ip4/127.0.0.1/tcp/7777/ws"; let address = "/ip4/127.0.0.1/tcp/7777/ws";
let address_string = fmt_as_address_string(address.parse().unwrap()) let address_string =
.expect("To be a formatted multi address. "); TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap()))
.unwrap()
.to_string();
assert_eq!(address_string, "127.0.0.1:7777"); assert_eq!(address_string, "127.0.0.1:7777");
} }
#[test] #[test]
fn dns4_to_address_string_should_be_some() { fn dns4_to_address_string_should_be_some() {
let address = "/dns4/randomdomain.com/tcp/7777"; let address = "/dns4/randomdomain.com/tcp/7777";
let address_string = fmt_as_address_string(address.parse().unwrap()) let address_string =
.expect("To be a formatted multi address. "); TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap()))
.unwrap()
.to_string();
assert_eq!(address_string, "randomdomain.com:7777"); assert_eq!(address_string, "randomdomain.com:7777");
} }
#[test] #[test]
fn dns_to_address_string_should_be_some() { fn dns_to_address_string_should_be_some() {
let address = "/dns/randomdomain.com/tcp/7777"; let address = "/dns/randomdomain.com/tcp/7777";
let address_string = fmt_as_address_string(address.parse().unwrap()) let address_string =
.expect("To be a formatted multi address. "); TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap()))
.unwrap()
.to_string();
assert_eq!(address_string, "randomdomain.com:7777"); assert_eq!(address_string, "randomdomain.com:7777");
} }
#[test] #[test]
fn dnsaddr_to_address_string_should_be_none() { fn dnsaddr_to_address_string_should_be_error() {
let address = "/dnsaddr/randomdomain.com"; let address = "/dnsaddr/randomdomain.com";
let address_string = fmt_as_address_string(address.parse().unwrap()).ok(); let _ =
assert_eq!(address_string, None); TorCompatibleAddress::from_multiaddr(Cow::Owned(address.parse().unwrap())).unwrap_err();
} }
} }