@@ -474,16 +474,20 @@ where
474474 }
475475}
476476
477- /// Same as [`connect_outbound`], using a SOCKS5 proxy
478- pub async fn socks5_connect_outbound < PM : Deref + ' static + Send + Sync + Clone > (
477+ /// Same as [`connect_outbound`], using a Tor SOCKS5 proxy. The stream isolation parameter is a
478+ /// username-password tuple which will be passed to the Tor SOCKS5 proxy during connection
479+ /// establishment. See https://spec.torproject.org/socks-extensions.html for further details.
480+ pub async fn tor_socks5_connect_outbound < PM : Deref + ' static + Send + Sync + Clone > (
479481 peer_manager : PM , their_node_id : PublicKey , socks5_proxy_addr : SocketAddr , addr : SocketAddress ,
480- user_pass : Option < ( & str , & str ) > ,
482+ stream_isolation_parameter : ( & str , & str ) ,
481483) -> Option < impl std:: future:: Future < Output = ( ) > >
482484where
483485 PM :: Target : APeerManager < Descriptor = SocketDescriptor > ,
484486{
485487 let connect_fut = async {
486- socks5_connect ( socks5_proxy_addr, addr, user_pass) . await . map ( |s| s. into_std ( ) . unwrap ( ) )
488+ tor_socks5_connect ( socks5_proxy_addr, addr, stream_isolation_parameter)
489+ . await
490+ . map ( |s| s. into_std ( ) . unwrap ( ) )
487491 } ;
488492 if let Ok ( Ok ( stream) ) =
489493 time:: timeout ( Duration :: from_secs ( SOCKS5_CONNECT_OUTBOUND_TIMEOUT ) , connect_fut) . await
@@ -494,8 +498,8 @@ where
494498 }
495499}
496500
497- async fn socks5_connect (
498- socks5_proxy_addr : SocketAddr , addr : SocketAddress , user_pass : Option < ( & str , & str ) > ,
501+ async fn tor_socks5_connect (
502+ socks5_proxy_addr : SocketAddr , addr : SocketAddress , stream_isolation_parameter : ( & str , & str ) ,
499503) -> Result < TcpStream , ( ) > {
500504 use std:: io:: { Cursor , Write } ;
501505 use tokio:: io:: AsyncReadExt ;
@@ -507,7 +511,6 @@ async fn socks5_connect(
507511 // Constants defined in RFC 1928 and RFC 1929
508512 const VERSION : u8 = 5 ;
509513 const NMETHODS : u8 = 1 ;
510- const NO_AUTH : u8 = 0 ;
511514 const USERNAME_PASSWORD_AUTH : u8 = 2 ;
512515 const METHOD_SELECT_REPLY_LEN : usize = 2 ;
513516 const USERNAME_PASSWORD_VERSION : u8 = 1 ;
@@ -526,40 +529,38 @@ async fn socks5_connect(
526529 const SOCKS5_REQUEST_MAX_LEN : usize = 1 /* VER */ + 1 /* CMD */ + 1 /* RSV */ + 1 /* ATYP */
527530 + 1 /* HOSTNAME len */ + HOSTNAME_MAX_LEN /* HOSTNAME */ + 2 /* PORT */ ;
528531
529- let selected_auth = if user_pass. is_some ( ) { USERNAME_PASSWORD_AUTH } else { NO_AUTH } ;
530- let method_selection_request = [ VERSION , NMETHODS , selected_auth] ;
532+ let ( username, password) = stream_isolation_parameter;
533+ if username. len ( ) > USERNAME_MAX_LEN || password. len ( ) > PASSWORD_MAX_LEN {
534+ return Err ( ( ) ) ;
535+ }
536+
537+ let method_selection_request = [ VERSION , NMETHODS , USERNAME_PASSWORD_AUTH ] ;
531538 let mut tcp_stream = TcpStream :: connect ( & socks5_proxy_addr) . await . map_err ( |_| ( ) ) ?;
532539 tokio:: io:: AsyncWriteExt :: write_all ( & mut tcp_stream, & method_selection_request)
533540 . await
534541 . map_err ( |_| ( ) ) ?;
535542
536543 let mut method_selection_reply = [ 0u8 ; METHOD_SELECT_REPLY_LEN ] ;
537544 tcp_stream. read_exact ( & mut method_selection_reply) . await . map_err ( |_| ( ) ) ?;
538- if method_selection_reply != [ VERSION , selected_auth ] {
545+ if method_selection_reply != [ VERSION , USERNAME_PASSWORD_AUTH ] {
539546 return Err ( ( ) ) ;
540547 }
541548
542- if let Some ( ( username, password) ) = user_pass {
543- if username. len ( ) > USERNAME_MAX_LEN || password. len ( ) > PASSWORD_MAX_LEN {
544- return Err ( ( ) ) ;
545- }
546-
547- let mut username_password_request = [ 0u8 ; USERNAME_PASSWORD_REQUEST_MAX_LEN ] ;
548- let mut writer = Cursor :: new ( & mut username_password_request[ ..] ) ;
549- writer. write_all ( & [ USERNAME_PASSWORD_VERSION , username. len ( ) as u8 ] ) . map_err ( |_| ( ) ) ?;
550- writer. write_all ( username. as_bytes ( ) ) . map_err ( |_| ( ) ) ?;
551- writer. write_all ( & [ password. len ( ) as u8 ] ) . map_err ( |_| ( ) ) ?;
552- writer. write_all ( password. as_bytes ( ) ) . map_err ( |_| ( ) ) ?;
553- let pos = writer. position ( ) as usize ;
554- tokio:: io:: AsyncWriteExt :: write_all ( & mut tcp_stream, & username_password_request[ ..pos] )
555- . await
556- . map_err ( |_| ( ) ) ?;
549+ let mut username_password_request = [ 0u8 ; USERNAME_PASSWORD_REQUEST_MAX_LEN ] ;
550+ let mut writer = Cursor :: new ( & mut username_password_request[ ..] ) ;
551+ writer. write_all ( & [ USERNAME_PASSWORD_VERSION , username. len ( ) as u8 ] ) . map_err ( |_| ( ) ) ?;
552+ writer. write_all ( username. as_bytes ( ) ) . map_err ( |_| ( ) ) ?;
553+ writer. write_all ( & [ password. len ( ) as u8 ] ) . map_err ( |_| ( ) ) ?;
554+ writer. write_all ( password. as_bytes ( ) ) . map_err ( |_| ( ) ) ?;
555+ let pos = writer. position ( ) as usize ;
556+ tokio:: io:: AsyncWriteExt :: write_all ( & mut tcp_stream, & username_password_request[ ..pos] )
557+ . await
558+ . map_err ( |_| ( ) ) ?;
557559
558- let mut username_password_reply = [ 0u8 ; USERNAME_PASSWORD_REPLY_LEN ] ;
559- tcp_stream. read_exact ( & mut username_password_reply) . await . map_err ( |_| ( ) ) ?;
560- if username_password_reply != [ USERNAME_PASSWORD_VERSION , SUCCESS ] {
561- return Err ( ( ) ) ;
562- }
560+ let mut username_password_reply = [ 0u8 ; USERNAME_PASSWORD_REPLY_LEN ] ;
561+ tcp_stream. read_exact ( & mut username_password_reply) . await . map_err ( |_| ( ) ) ?;
562+ if username_password_reply != [ USERNAME_PASSWORD_VERSION , SUCCESS ] {
563+ return Err ( ( ) ) ;
563564 }
564565
565566 let mut socks5_request = [ 0u8 ; SOCKS5_REQUEST_MAX_LEN ] ;
@@ -755,7 +756,7 @@ impl Hash for SocketDescriptor {
755756#[ cfg( test) ]
756757mod tests {
757758 #[ cfg( tor_socks5) ]
758- use super :: socks5_connect ;
759+ use super :: tor_socks5_connect ;
759760
760761 use bitcoin:: constants:: ChainHash ;
761762 use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
@@ -1095,32 +1096,30 @@ mod tests {
10951096
10961097 #[ cfg( tor_socks5) ]
10971098 #[ tokio:: test]
1098- async fn test_socks5_connect ( ) {
1099+ async fn test_tor_socks5_connect ( ) {
10991100 // Set TOR_SOCKS5_PROXY=127.0.0.1:9050
11001101 let socks5_proxy_addr: SocketAddr = std:: env!( "TOR_SOCKS5_PROXY" ) . parse ( ) . unwrap ( ) ;
11011102
11021103 // Success cases
11031104
1104- for ( addr_str, user_pass ) in [
1105+ for ( addr_str, stream_isolation_parameter ) in [
11051106 // google.com
1106- ( "142.250.189.196:80" , None ) ,
1107+ ( "142.250.189.196:80" , ( "<torS0X>0" , "" ) ) ,
11071108 // google.com
1108- ( "[2607:f8b0:4005:813::2004]:80" , None ) ,
1109+ ( "[2607:f8b0:4005:813::2004]:80" , ( "<torS0X>0" , "123" ) ) ,
11091110 // torproject.org
1110- ( "torproject.org:80" , None ) ,
1111+ ( "torproject.org:80" , ( "<torS0X>1abc" , "" ) ) ,
11111112 // torproject.org
1112- ( "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" , None ) ,
1113- // Same vectors as above, with a username and password
1114- ( "142.250.189.196:80" , Some ( ( "<torS0X>0" , "" ) ) ) ,
1115- ( "[2607:f8b0:4005:813::2004]:80" , Some ( ( "<torS0X>0" , "123" ) ) ) ,
1116- ( "torproject.org:80" , Some ( ( "<torS0X>1abc" , "" ) ) ) ,
11171113 (
11181114 "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
1119- Some ( ( "<torS0X>1abc" , "123" ) ) ,
1115+ ( "<torS0X>1abc" , "123" ) ,
11201116 ) ,
11211117 ] {
11221118 let addr: SocketAddress = addr_str. parse ( ) . unwrap ( ) ;
1123- let tcp_stream = socks5_connect ( socks5_proxy_addr, addr, user_pass) . await . unwrap ( ) ;
1119+ let tcp_stream =
1120+ tor_socks5_connect ( socks5_proxy_addr, addr, stream_isolation_parameter)
1121+ . await
1122+ . unwrap ( ) ;
11241123 assert_eq ! (
11251124 tcp_stream. try_read( & mut [ 0u8 ; 1 ] ) . unwrap_err( ) . kind( ) ,
11261125 std:: io:: ErrorKind :: WouldBlock
@@ -1129,22 +1128,17 @@ mod tests {
11291128
11301129 // Failure cases
11311130
1132- for ( addr_str, user_pass ) in [
1131+ for ( addr_str, stream_isolation_parameter ) in [
11331132 // google.com, with some invalid port
1134- ( "142.250.189.196:1234" , None ) ,
1133+ ( "142.250.189.196:1234" , ( "<torS0X>0" , "" ) ) ,
11351134 // google.com, with some invalid port
1136- ( "[2607:f8b0:4005:813::2004]:1234" , None ) ,
1135+ ( "[2607:f8b0:4005:813::2004]:1234" , ( "<torS0X>0" , "123" ) ) ,
11371136 // torproject.org, with some invalid port
1138- ( "torproject.org:1234" , None ) ,
1137+ ( "torproject.org:1234" , ( "<torS0X>1abc" , "" ) ) ,
11391138 // torproject.org, with a typo
1140- ( "3gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" , None ) ,
1141- // Same vectors as above, with a username and password
1142- ( "142.250.189.196:1234" , Some ( ( "<torS0X>0" , "" ) ) ) ,
1143- ( "[2607:f8b0:4005:813::2004]:1234" , Some ( ( "<torS0X>0" , "123" ) ) ) ,
1144- ( "torproject.org:1234" , Some ( ( "<torS0X>1abc" , "" ) ) ) ,
11451139 (
11461140 "3gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
1147- Some ( ( "<torS0X>1abc" , "123" ) ) ,
1141+ ( "<torS0X>1abc" , "123" ) ,
11481142 ) ,
11491143 /* TODO: Uncomment when format types 30 and 31 land in tor stable, see https://spec.torproject.org/socks-extensions.html,
11501144 these are invalid usernames according to those standards.
@@ -1155,7 +1149,9 @@ mod tests {
11551149 */
11561150 ] {
11571151 let addr: SocketAddress = addr_str. parse ( ) . unwrap ( ) ;
1158- assert ! ( socks5_connect( socks5_proxy_addr, addr, user_pass) . await . is_err( ) ) ;
1152+ assert ! ( tor_socks5_connect( socks5_proxy_addr, addr, stream_isolation_parameter)
1153+ . await
1154+ . is_err( ) ) ;
11591155 }
11601156 }
11611157}
0 commit comments