@ -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,13 +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 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 {
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 ( ( Ipv4Addr ::LOCALHOST , self . socks_port ) , tor_ address_string)
Socks5Stream ::connect ( ( Ipv4Addr ::LOCALHOST , self . socks_port ) , address. to _string( ) )
. await
. await
. map_err ( | e | io ::Error ::new ( io ::ErrorKind ::ConnectionRefused , e ) ) ? ;
. 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.
/// 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 ( ) {
}
// 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 ( )
) )
}
// 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 TorCompatibleAddress {
Some ( Protocol ::Tcp ( port ) | Protocol ::Udp ( port ) ) = > port ,
/// Constructs a new [`TorCompatibleAddress`] from a [`Multiaddr`].
_ = > return Err ( TransportError ::MultiaddrNotSupported ( multi ) ) ,
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 ( ) ) ) ,
}
}
Ok ( format! ( "{}:{}" , address_string , port ) )
/// 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 ( ) ,
}
}
}
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) ]
#[ cfg(test) ]
@ -95,69 +136,76 @@ pub mod test {
#[ test ]
#[ test ]
fn test_tor_address_string ( ) {
fn test_tor_address_string ( ) {
let address =
let address = tor_compatible_address_from_str ( "/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9" ) ;
"/onion3/oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did:1024/p2p/12D3KooWPD4uHN74SHotLN7VCH7Fm8zZgaNVymYcpeF1fpD2guc9"
;
assert! ( ! address . is_certainly_not_reachable_via_tor_daemon ( ) ) ;
let address_string = fmt_as_address_string ( address . parse ( ) . unwrap ( ) )
. expect ( "To be a multi formatted address." ) ;
assert_eq! (
assert_eq! (
address _string,
address . to_string ( ) ,
"oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024"
"oarchy4tamydxcitaki6bc2v4leza6v35iezmu2chg2bap63sv6f2did.onion:1024"
) ;
) ;
}
}
#[ 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 = tor_compatible_address_from_str ( "/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! ( address . is_certainly_not_reachable_via_tor_daemon ( ) ) ;
assert_eq! ( address _string, "127.0.0.1:7777" ) ;
assert_eq! ( address . to _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 =
let address_string = fmt_as_address_string ( address . parse ( ) . unwrap ( ) )
tor_compatible_address_from_str ( "/ip6/2001:db8:85a3:8d3:1319:8a2e:370:7348/tcp/7777" ) ;
. expect ( "To be a formatted multi address. " ) ;
assert_eq! ( address_string , "2001:db8:85a3:8d3:1319:8a2e:370:7348: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 ]
#[ 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 = tor_compatible_address_from_str ( "/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! ( address . is_certainly_not_reachable_via_tor_daemon ( ) ) ;
assert_eq! ( address _string, "127.0.0.1:7777" ) ;
assert_eq! ( address . to _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 = tor_compatible_address_from_str ( "/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! ( address . is_certainly_not_reachable_via_tor_daemon ( ) ) ;
assert_eq! ( address _string, "127.0.0.1:7777" ) ;
assert_eq! ( address . to _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 = tor_compatible_address_from_str ( "/dns4/randomdomain.com/tcp/7777" ) ;
let address_string = fmt_as_address_string ( address . parse ( ) . unwrap ( ) )
. expect ( "To be a formatted multi address. " ) ;
assert! ( ! address . is_certainly_not_reachable_via_tor_daemon ( ) ) ;
assert_eq! ( address _string, "randomdomain.com:7777" ) ;
assert_eq! ( address . to _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 = tor_compatible_address_from_str ( "/dns/randomdomain.com/tcp/7777" ) ;
let address_string = fmt_as_address_string ( address . parse ( ) . unwrap ( ) )
. expect ( "To be a formatted multi address. " ) ;
assert! ( ! address . is_certainly_not_reachable_via_tor_daemon ( ) ) ;
assert_eq! ( address _string, "randomdomain.com:7777" ) ;
assert_eq! ( address . to _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 ( ) ;
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 ( )
}
}
}
}