@@ -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,19 @@ 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+ /// See <https://spec.torproject.org/socks-extensions.html> for further details.
481+ pub async fn tor_connect_outbound < PM : Deref + ' static + Send + Sync + Clone , ES : Deref > (
482+ peer_manager : PM , their_node_id : PublicKey , tor_proxy_addr : SocketAddr , addr : SocketAddress ,
483+ entropy_source : ES ,
481484) -> Option < impl std:: future:: Future < Output = ( ) > >
482485where
483486 PM :: Target : APeerManager < Descriptor = SocketDescriptor > ,
487+ ES :: Target : EntropySource ,
484488{
485489 let connect_fut = async {
486- socks5_connect ( socks5_proxy_addr , addr, user_pass ) . await . map ( |s| s. into_std ( ) . unwrap ( ) )
490+ tor_connect ( tor_proxy_addr , addr, entropy_source ) . await . 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,9 +498,12 @@ where
494498 }
495499}
496500
497- async fn socks5_connect (
498- socks5_proxy_addr : SocketAddr , addr : SocketAddress , user_pass : Option < ( & str , & str ) > ,
499- ) -> Result < TcpStream , ( ) > {
501+ async fn tor_connect < ES : Deref > (
502+ tor_proxy_addr : SocketAddr , addr : SocketAddress , entropy_source : ES ,
503+ ) -> Result < TcpStream , ( ) >
504+ where
505+ ES :: Target : EntropySource ,
506+ {
500507 use std:: io:: { Cursor , Write } ;
501508 use tokio:: io:: AsyncReadExt ;
502509
@@ -507,7 +514,6 @@ async fn socks5_connect(
507514 // Constants defined in RFC 1928 and RFC 1929
508515 const VERSION : u8 = 5 ;
509516 const NMETHODS : u8 = 1 ;
510- const NO_AUTH : u8 = 0 ;
511517 const USERNAME_PASSWORD_AUTH : u8 = 2 ;
512518 const METHOD_SELECT_REPLY_LEN : usize = 2 ;
513519 const USERNAME_PASSWORD_VERSION : u8 = 1 ;
@@ -518,48 +524,47 @@ async fn socks5_connect(
518524 const ATYP_DOMAINNAME : u8 = 3 ;
519525 const ATYP_IPV6 : u8 = 4 ;
520526 const SUCCESS : u8 = 0 ;
521- const USERNAME_MAX_LEN : usize = 255 ;
522- const PASSWORD_MAX_LEN : usize = 255 ;
523527
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 */ ;
528+ // Tor extensions, see https://spec.torproject.org/socks-extensions.html for further details
529+ const USERNAME : & [ u8 ] = b"<torS0X>0" ;
530+ const USERNAME_LEN : usize = USERNAME . len ( ) ;
531+ const PASSWORD_LEN : usize = 32 ;
532+
533+ const USERNAME_PASSWORD_REQUEST_LEN : usize = 1 /* VER */ + 1 /* ULEN */ + USERNAME_LEN /* UNAME max len */
534+ + 1 /* PLEN */ + PASSWORD_LEN /* PASSWD max len */ ;
526535 const SOCKS5_REQUEST_MAX_LEN : usize = 1 /* VER */ + 1 /* CMD */ + 1 /* RSV */ + 1 /* ATYP */
527536 + 1 /* HOSTNAME len */ + HOSTNAME_MAX_LEN /* HOSTNAME */ + 2 /* PORT */ ;
528537
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 ( |_| ( ) ) ?;
538+ let password: [ u8 ; 32 ] = entropy_source. get_secure_random_bytes ( ) ;
539+
540+ let method_selection_request = [ VERSION , NMETHODS , USERNAME_PASSWORD_AUTH ] ;
541+ let mut tcp_stream = TcpStream :: connect ( & tor_proxy_addr) . await . map_err ( |_| ( ) ) ?;
532542 tokio:: io:: AsyncWriteExt :: write_all ( & mut tcp_stream, & method_selection_request)
533543 . await
534544 . map_err ( |_| ( ) ) ?;
535545
536546 let mut method_selection_reply = [ 0u8 ; METHOD_SELECT_REPLY_LEN ] ;
537547 tcp_stream. read_exact ( & mut method_selection_reply) . await . map_err ( |_| ( ) ) ?;
538- if method_selection_reply != [ VERSION , selected_auth ] {
548+ if method_selection_reply != [ VERSION , USERNAME_PASSWORD_AUTH ] {
539549 return Err ( ( ) ) ;
540550 }
541551
542- if let Some ( ( username, password) ) = user_pass {
543- if username. len ( ) > USERNAME_MAX_LEN || password. len ( ) > PASSWORD_MAX_LEN {
544- return Err ( ( ) ) ;
545- }
552+ let mut username_password_request = [ 0u8 ; USERNAME_PASSWORD_REQUEST_LEN ] ;
553+ let mut writer = Cursor :: new ( & mut username_password_request[ ..] ) ;
554+ writer. write_all ( & [ USERNAME_PASSWORD_VERSION , USERNAME_LEN as u8 ] ) . map_err ( |_| ( ) ) ?;
555+ writer. write_all ( USERNAME ) . map_err ( |_| ( ) ) ?;
556+ writer. write_all ( & [ PASSWORD_LEN as u8 ] ) . map_err ( |_| ( ) ) ?;
557+ writer. write_all ( & password) . map_err ( |_| ( ) ) ?;
558+ let pos = writer. position ( ) as usize ;
559+ debug_assert_eq ! ( pos, USERNAME_PASSWORD_REQUEST_LEN ) ;
560+ tokio:: io:: AsyncWriteExt :: write_all ( & mut tcp_stream, & username_password_request[ ..pos] )
561+ . await
562+ . map_err ( |_| ( ) ) ?;
546563
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- }
564+ let mut username_password_reply = [ 0u8 ; USERNAME_PASSWORD_REPLY_LEN ] ;
565+ tcp_stream. read_exact ( & mut username_password_reply) . await . map_err ( |_| ( ) ) ?;
566+ if username_password_reply != [ USERNAME_PASSWORD_VERSION , SUCCESS ] {
567+ return Err ( ( ) ) ;
563568 }
564569
565570 let mut socks5_request = [ 0u8 ; SOCKS5_REQUEST_MAX_LEN ] ;
@@ -754,9 +759,6 @@ impl Hash for SocketDescriptor {
754759
755760#[ cfg( test) ]
756761mod tests {
757- #[ cfg( tor_socks5) ]
758- use super :: socks5_connect;
759-
760762 use bitcoin:: constants:: ChainHash ;
761763 use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
762764 use bitcoin:: Network ;
@@ -770,8 +772,6 @@ mod tests {
770772 use tokio:: sync:: mpsc;
771773
772774 use std:: mem;
773- #[ cfg( tor_socks5) ]
774- use std:: net:: SocketAddr ;
775775 use std:: sync:: atomic:: { AtomicBool , Ordering } ;
776776 use std:: sync:: { Arc , Mutex } ;
777777 use std:: time:: Duration ;
@@ -1093,34 +1093,40 @@ mod tests {
10931093 race_disconnect_accept ( ) . await ;
10941094 }
10951095
1096- #[ cfg( tor_socks5 ) ]
1096+ #[ cfg( tor ) ]
10971097 #[ 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 ( ) ;
1098+ async fn test_tor_connect ( ) {
1099+ use super :: tor_connect;
1100+ use lightning:: sign:: EntropySource ;
1101+ use std:: net:: SocketAddr ;
1102+
1103+ // Set TOR_PROXY=127.0.0.1:9050
1104+ let tor_proxy_addr: SocketAddr = std:: env!( "TOR_PROXY" ) . parse ( ) . unwrap ( ) ;
1105+
1106+ struct TestEntropySource ;
1107+
1108+ impl EntropySource for TestEntropySource {
1109+ fn get_secure_random_bytes ( & self ) -> [ u8 ; 32 ] {
1110+ [ 0xffu8 ; 32 ]
1111+ }
1112+ }
1113+
1114+ let entropy_source = TestEntropySource ;
11011115
11021116 // Success cases
11031117
1104- for ( addr_str, user_pass ) in [
1118+ for addr_str in [
11051119 // google.com
1106- ( "142.250.189.196:80" , None ) ,
1120+ "142.250.189.196:80" ,
11071121 // google.com
1108- ( "[2607:f8b0:4005:813::2004]:80" , None ) ,
1122+ "[2607:f8b0:4005:813::2004]:80" ,
11091123 // torproject.org
1110- ( "torproject.org:80" , None ) ,
1124+ "torproject.org:80" ,
11111125 // 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- ) ,
1126+ "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
11211127 ] {
11221128 let addr: SocketAddress = addr_str. parse ( ) . unwrap ( ) ;
1123- let tcp_stream = socks5_connect ( socks5_proxy_addr , addr, user_pass ) . await . unwrap ( ) ;
1129+ let tcp_stream = tor_connect ( tor_proxy_addr , addr, & entropy_source ) . await . unwrap ( ) ;
11241130 assert_eq ! (
11251131 tcp_stream. try_read( & mut [ 0u8 ; 1 ] ) . unwrap_err( ) . kind( ) ,
11261132 std:: io:: ErrorKind :: WouldBlock
@@ -1129,33 +1135,18 @@ mod tests {
11291135
11301136 // Failure cases
11311137
1132- for ( addr_str, user_pass ) in [
1138+ for addr_str in [
11331139 // google.com, with some invalid port
1134- ( "142.250.189.196:1234" , None ) ,
1140+ "142.250.189.196:1234" ,
11351141 // google.com, with some invalid port
1136- ( "[2607:f8b0:4005:813::2004]:1234" , None ) ,
1142+ "[2607:f8b0:4005:813::2004]:1234" ,
11371143 // torproject.org, with some invalid port
1138- ( "torproject.org:1234" , None ) ,
1144+ "torproject.org:1234" ,
11391145 // 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- */
1146+ "3gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
11561147 ] {
11571148 let addr: SocketAddress = addr_str. parse ( ) . unwrap ( ) ;
1158- assert ! ( socks5_connect ( socks5_proxy_addr , addr, user_pass ) . await . is_err( ) ) ;
1149+ assert ! ( tor_connect ( tor_proxy_addr , addr, & entropy_source ) . await . is_err( ) ) ;
11591150 }
11601151 }
11611152}
0 commit comments