@@ -37,6 +37,7 @@ use lightning::ln::msgs::SocketAddress;
3737use lightning:: ln:: peer_handler;
3838use lightning:: ln:: peer_handler:: APeerManager ;
3939use lightning:: ln:: peer_handler:: SocketDescriptor as LnSocketTrait ;
40+ use lightning:: sign:: EntropySource ;
4041
4142use std:: future:: Future ;
4243use std:: hash:: Hash ;
@@ -474,16 +475,18 @@ where
474475 }
475476}
476477
477- /// Same as [`connect_outbound`], using a SOCKS5 proxy
478- pub async fn socks5_connect_outbound < PM : Deref + ' static + Send + Sync + Clone > (
479- peer_manager : PM , their_node_id : PublicKey , socks5_proxy_addr : SocketAddr , addr : SocketAddress ,
480- user_pass : Option < ( & str , & str ) > ,
478+ /// Routes [`connect_outbound`] through Tor. Implements stream isolation for each connection
479+ /// using a stream isolation parameter sourced from [`EntropySource::get_secure_random_bytes`].
480+ pub async fn tor_connect_outbound < PM : Deref + ' static + Send + Sync + Clone , ES : Deref > (
481+ peer_manager : PM , their_node_id : PublicKey , addr : SocketAddress , tor_proxy_addr : SocketAddr ,
482+ entropy_source : ES ,
481483) -> Option < impl std:: future:: Future < Output = ( ) > >
482484where
483485 PM :: Target : APeerManager < Descriptor = SocketDescriptor > ,
486+ ES :: Target : EntropySource ,
484487{
485488 let connect_fut = async {
486- socks5_connect ( socks5_proxy_addr , addr , user_pass ) . await . map ( |s| s. into_std ( ) . unwrap ( ) )
489+ tor_connect ( addr , tor_proxy_addr , entropy_source ) . await . map ( |s| s. into_std ( ) . unwrap ( ) )
487490 } ;
488491 if let Ok ( Ok ( stream) ) =
489492 time:: timeout ( Duration :: from_secs ( SOCKS5_CONNECT_OUTBOUND_TIMEOUT ) , connect_fut) . await
@@ -494,9 +497,12 @@ where
494497 }
495498}
496499
497- async fn socks5_connect (
498- socks5_proxy_addr : SocketAddr , addr : SocketAddress , user_pass : Option < ( & str , & str ) > ,
499- ) -> Result < TcpStream , ( ) > {
500+ async fn tor_connect < ES : Deref > (
501+ addr : SocketAddress , tor_proxy_addr : SocketAddr , entropy_source : ES ,
502+ ) -> Result < TcpStream , ( ) >
503+ where
504+ ES :: Target : EntropySource ,
505+ {
500506 use std:: io:: { Cursor , Write } ;
501507 use tokio:: io:: AsyncReadExt ;
502508
@@ -507,7 +513,6 @@ async fn socks5_connect(
507513 // Constants defined in RFC 1928 and RFC 1929
508514 const VERSION : u8 = 5 ;
509515 const NMETHODS : u8 = 1 ;
510- const NO_AUTH : u8 = 0 ;
511516 const USERNAME_PASSWORD_AUTH : u8 = 2 ;
512517 const METHOD_SELECT_REPLY_LEN : usize = 2 ;
513518 const USERNAME_PASSWORD_VERSION : u8 = 1 ;
@@ -518,48 +523,45 @@ async fn socks5_connect(
518523 const ATYP_DOMAINNAME : u8 = 3 ;
519524 const ATYP_IPV6 : u8 = 4 ;
520525 const SUCCESS : u8 = 0 ;
521- const USERNAME_MAX_LEN : usize = 255 ;
522- const PASSWORD_MAX_LEN : usize = 255 ;
523526
524- const USERNAME_PASSWORD_REQUEST_MAX_LEN : usize = 1 /* VER */ + 1 /* ULEN */ + USERNAME_MAX_LEN /* UNAME max len */
525- + 1 /* PLEN */ + PASSWORD_MAX_LEN /* PASSWD max len */ ;
527+ // Tor extensions, see https://spec.torproject.org/socks-extensions.html for further details
528+ const USERNAME : & [ u8 ] = b"<torS0X>0" ;
529+ const USERNAME_LEN : usize = USERNAME . len ( ) ;
530+ const PASSWORD_LEN : usize = 32 ;
531+
532+ const USERNAME_PASSWORD_REQUEST_LEN : usize =
533+ 1 /* VER */ + 1 /* ULEN */ + USERNAME_LEN + 1 /* PLEN */ + PASSWORD_LEN ;
526534 const SOCKS5_REQUEST_MAX_LEN : usize = 1 /* VER */ + 1 /* CMD */ + 1 /* RSV */ + 1 /* ATYP */
527535 + 1 /* HOSTNAME len */ + HOSTNAME_MAX_LEN /* HOSTNAME */ + 2 /* PORT */ ;
528536
529- let selected_auth = if user_pass. is_some ( ) { USERNAME_PASSWORD_AUTH } else { NO_AUTH } ;
530- let method_selection_request = [ VERSION , NMETHODS , selected_auth] ;
531- let mut tcp_stream = TcpStream :: connect ( & socks5_proxy_addr) . await . map_err ( |_| ( ) ) ?;
537+ let method_selection_request = [ VERSION , NMETHODS , USERNAME_PASSWORD_AUTH ] ;
538+ let mut tcp_stream = TcpStream :: connect ( & tor_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- }
549+ let password: [ u8 ; PASSWORD_LEN ] = entropy_source. get_secure_random_bytes ( ) ;
550+ let mut username_password_request = [ 0u8 ; USERNAME_PASSWORD_REQUEST_LEN ] ;
551+ let mut writer = Cursor :: new ( & mut username_password_request[ ..] ) ;
552+ writer. write_all ( & [ USERNAME_PASSWORD_VERSION , USERNAME_LEN as u8 ] ) . map_err ( |_| ( ) ) ?;
553+ writer. write_all ( USERNAME ) . map_err ( |_| ( ) ) ?;
554+ writer. write_all ( & [ PASSWORD_LEN as u8 ] ) . map_err ( |_| ( ) ) ?;
555+ writer. write_all ( & password) . map_err ( |_| ( ) ) ?;
556+ debug_assert_eq ! ( writer. position( ) as usize , USERNAME_PASSWORD_REQUEST_LEN ) ;
557+ tokio:: io:: AsyncWriteExt :: write_all ( & mut tcp_stream, & username_password_request)
558+ . await
559+ . map_err ( |_| ( ) ) ?;
546560
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 ( |_| ( ) ) ?;
557-
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- }
561+ let mut username_password_reply = [ 0u8 ; USERNAME_PASSWORD_REPLY_LEN ] ;
562+ tcp_stream. read_exact ( & mut username_password_reply) . await . map_err ( |_| ( ) ) ?;
563+ if username_password_reply != [ USERNAME_PASSWORD_VERSION , SUCCESS ] {
564+ return Err ( ( ) ) ;
563565 }
564566
565567 let mut socks5_request = [ 0u8 ; SOCKS5_REQUEST_MAX_LEN ] ;
@@ -754,9 +756,6 @@ impl Hash for SocketDescriptor {
754756
755757#[ cfg( test) ]
756758mod tests {
757- #[ cfg( tor_socks5) ]
758- use super :: socks5_connect;
759-
760759 use bitcoin:: constants:: ChainHash ;
761760 use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
762761 use bitcoin:: Network ;
@@ -770,8 +769,6 @@ mod tests {
770769 use tokio:: sync:: mpsc;
771770
772771 use std:: mem;
773- #[ cfg( tor_socks5) ]
774- use std:: net:: SocketAddr ;
775772 use std:: sync:: atomic:: { AtomicBool , Ordering } ;
776773 use std:: sync:: { Arc , Mutex } ;
777774 use std:: time:: Duration ;
@@ -1093,34 +1090,40 @@ mod tests {
10931090 race_disconnect_accept ( ) . await ;
10941091 }
10951092
1096- #[ cfg( tor_socks5 ) ]
1093+ #[ cfg( tor ) ]
10971094 #[ tokio:: test]
1098- async fn test_socks5_connect ( ) {
1099- // Set TOR_SOCKS5_PROXY=127.0.0.1:9050
1100- let socks5_proxy_addr: SocketAddr = std:: env!( "TOR_SOCKS5_PROXY" ) . parse ( ) . unwrap ( ) ;
1095+ async fn test_tor_connect ( ) {
1096+ use super :: tor_connect;
1097+ use lightning:: sign:: EntropySource ;
1098+ use std:: net:: SocketAddr ;
1099+
1100+ // Set TOR_PROXY=127.0.0.1:9050
1101+ let tor_proxy_addr: SocketAddr = std:: env!( "TOR_PROXY" ) . parse ( ) . unwrap ( ) ;
1102+
1103+ struct TestEntropySource ;
1104+
1105+ impl EntropySource for TestEntropySource {
1106+ fn get_secure_random_bytes ( & self ) -> [ u8 ; 32 ] {
1107+ [ 0xffu8 ; 32 ]
1108+ }
1109+ }
1110+
1111+ let entropy_source = TestEntropySource ;
11011112
11021113 // Success cases
11031114
1104- for ( addr_str, user_pass ) in [
1115+ for addr_str in [
11051116 // google.com
1106- ( "142.250.189.196:80" , None ) ,
1117+ "142.250.189.196:80" ,
11071118 // google.com
1108- ( "[2607:f8b0:4005:813::2004]:80" , None ) ,
1119+ "[2607:f8b0:4005:813::2004]:80" ,
11091120 // torproject.org
1110- ( "torproject.org:80" , None ) ,
1121+ "torproject.org:80" ,
11111122 // 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" , "" ) ) ) ,
1117- (
1118- "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
1119- Some ( ( "<torS0X>1abc" , "123" ) ) ,
1120- ) ,
1123+ "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
11211124 ] {
11221125 let addr: SocketAddress = addr_str. parse ( ) . unwrap ( ) ;
1123- let tcp_stream = socks5_connect ( socks5_proxy_addr , addr , user_pass ) . await . unwrap ( ) ;
1126+ let tcp_stream = tor_connect ( addr , tor_proxy_addr , & entropy_source ) . await . unwrap ( ) ;
11241127 assert_eq ! (
11251128 tcp_stream. try_read( & mut [ 0u8 ; 1 ] ) . unwrap_err( ) . kind( ) ,
11261129 std:: io:: ErrorKind :: WouldBlock
@@ -1129,33 +1132,18 @@ mod tests {
11291132
11301133 // Failure cases
11311134
1132- for ( addr_str, user_pass ) in [
1135+ for addr_str in [
11331136 // google.com, with some invalid port
1134- ( "142.250.189.196:1234" , None ) ,
1137+ "142.250.189.196:1234" ,
11351138 // google.com, with some invalid port
1136- ( "[2607:f8b0:4005:813::2004]:1234" , None ) ,
1139+ "[2607:f8b0:4005:813::2004]:1234" ,
11371140 // torproject.org, with some invalid port
1138- ( "torproject.org:1234" , None ) ,
1141+ "torproject.org:1234" ,
11391142 // 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" , "" ) ) ) ,
1145- (
1146- "3gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
1147- Some ( ( "<torS0X>1abc" , "123" ) ) ,
1148- ) ,
1149- /* TODO: Uncomment when format types 30 and 31 land in tor stable, see https://spec.torproject.org/socks-extensions.html,
1150- these are invalid usernames according to those standards.
1151- ("142.250.189.196:80", Some(("<torS0X>0abc", "123"))),
1152- ("[2607:f8b0:4005:813::2004]:80", Some(("<torS0X>1", "123"))),
1153- ("torproject.org:80", Some(("<torS0X>9", "123"))),
1154- ("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80", Some(("<torS0X>", "123"))),
1155- */
1143+ "3gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
11561144 ] {
11571145 let addr: SocketAddress = addr_str. parse ( ) . unwrap ( ) ;
1158- assert ! ( socks5_connect ( socks5_proxy_addr , addr , user_pass ) . await . is_err( ) ) ;
1146+ assert ! ( tor_connect ( addr , tor_proxy_addr , & entropy_source ) . await . is_err( ) ) ;
11591147 }
11601148 }
11611149}
0 commit comments