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::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<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 {
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<String, TransportError<io::Error>> {
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<Self, TransportError<io::Error>> {
match multi.iter().collect::<Vec<_>>().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();
}
}