@@ -477,12 +477,14 @@ where
477477/// Same as [`connect_outbound`], using a SOCKS5 proxy
478478pub async fn socks5_connect_outbound < PM : Deref + ' static + Send + Sync + Clone > (
479479 peer_manager : PM , their_node_id : PublicKey , socks5_proxy_addr : SocketAddr , addr : SocketAddress ,
480+ user_pass : Option < ( & str , & str ) > ,
480481) -> Option < impl std:: future:: Future < Output = ( ) > >
481482where
482483 PM :: Target : APeerManager < Descriptor = SocketDescriptor > ,
483484{
484- let connect_fut =
485- async { socks5_connect ( socks5_proxy_addr, addr) . await . map ( |s| s. into_std ( ) . unwrap ( ) ) } ;
485+ let connect_fut = async {
486+ socks5_connect ( socks5_proxy_addr, addr, user_pass) . await . map ( |s| s. into_std ( ) . unwrap ( ) )
487+ } ;
486488 if let Ok ( Ok ( stream) ) =
487489 time:: timeout ( Duration :: from_secs ( SOCKS5_CONNECT_OUTBOUND_TIMEOUT ) , connect_fut) . await
488490 {
@@ -493,23 +495,28 @@ where
493495}
494496
495497async fn socks5_connect (
496- socks5_proxy_addr : SocketAddr , addr : SocketAddress ,
498+ socks5_proxy_addr : SocketAddr , addr : SocketAddress , user_pass : Option < ( & str , & str ) > ,
497499) -> Result < TcpStream , ( ) > {
498500 use tokio:: io:: AsyncReadExt ;
499501 use tokio:: io:: AsyncWriteExt ;
500502
501- // Constants defined in RFC 1928
503+ // Constants defined in RFC 1928 and RFC 1929
502504 const VERSION : u8 = 5 ;
503505 const NMETHODS : u8 = 1 ;
504506 const NO_AUTH : u8 = 0 ;
505- const METHOD_SELECT_RES_LEN : usize = 2 ;
507+ const USERNAME_PASSWORD_AUTH : u8 = 2 ;
508+ const METHOD_SELECT_REPLY_LEN : usize = 2 ;
509+ const USERNAME_PASSWORD_VERSION : u8 = 1 ;
510+ const USERNAME_PASSWORD_REPLY_LEN : usize = 2 ;
506511 const CMD_CONNECT : u8 = 1 ;
507512 const RSV : u8 = 0 ;
508513 const ATYP_IPV4 : u8 = 1 ;
509514 const ATYP_DOMAINNAME : u8 = 3 ;
510515 const ATYP_IPV6 : u8 = 4 ;
511- const SUCCEEDED : u8 = 0 ;
516+ const SUCCESS : u8 = 0 ;
512517
518+ const USERNAME_PASSWORD_REQUEST_MAX_LEN : usize = 1 /* VER */ + 1 /* ULEN */ + 255 /* UNAME max len */
519+ + 1 /* PLEN */ + 255 /* PASSWD max len */ ;
513520 const SOCKS5_REQUEST_REPLY_MAX_LEN : usize = 1 /* VER */ + 1 /* CMD for request, REP for reply */ + 1 /* RSV */
514521 + 1 /* ATYP */ + 1 /* HOSTNAME len */ + 255 /* HOSTNAME */ + 2 /* PORT */ ;
515522 const SOCKS5_REQUEST_REPLY_MIN_LEN : usize = 1 /* VER */ + 1 /* CMD for request, REP for reply */ + 1 /* RSV */
@@ -521,33 +528,62 @@ async fn socks5_connect(
521528 const SOCKS5_HOSTNAME_REQUEST_STATIC_FIELDS_LEN : usize = 1 /* VER */ + 1 /* CMD */ + 1 /* RSV */ + 1 /* ATYP */
522529 + 1 /* HOSTNAME len */ + 2 /* DST.PORT */ ;
523530
524- let method_selection_message = [ VERSION , NMETHODS , NO_AUTH ] ;
531+ let selected_auth = if user_pass. is_some ( ) { USERNAME_PASSWORD_AUTH } else { NO_AUTH } ;
532+ let method_selection_request = [ VERSION , NMETHODS , selected_auth] ;
525533 let mut tcp_stream = TcpStream :: connect ( & socks5_proxy_addr) . await . map_err ( |_| ( ) ) ?;
526- tcp_stream. write_all ( & method_selection_message ) . await . map_err ( |_| ( ) ) ?;
534+ tcp_stream. write_all ( & method_selection_request ) . await . map_err ( |_| ( ) ) ?;
527535
528- let mut method_selection_response = [ 0u8 ; METHOD_SELECT_RES_LEN ] ;
529- let n_read = tcp_stream. read_exact ( & mut method_selection_response ) . await . map_err ( |_| ( ) ) ?;
530- if n_read != METHOD_SELECT_RES_LEN || method_selection_response != [ VERSION , NO_AUTH ] {
536+ let mut method_selection_reply = [ 0u8 ; METHOD_SELECT_REPLY_LEN ] ;
537+ let n_read = tcp_stream. read_exact ( & mut method_selection_reply ) . await . map_err ( |_| ( ) ) ?;
538+ if n_read != METHOD_SELECT_REPLY_LEN || method_selection_reply != [ VERSION , selected_auth ] {
531539 return Err ( ( ) ) ;
532540 }
533541
534- let mut socks5_request = [ 0u8 ; SOCKS5_REQUEST_REPLY_MAX_LEN ] ;
535- let request_size;
542+ if let Some ( ( username, password) ) = user_pass {
543+ if username. len ( ) > 255 || password. len ( ) > 255 {
544+ return Err ( ( ) ) ;
545+ }
536546
547+ let mut username_password_request = [ 0u8 ; USERNAME_PASSWORD_REQUEST_MAX_LEN ] ;
548+ username_password_request[ 0 ] = USERNAME_PASSWORD_VERSION ;
549+ username_password_request[ 1 ] = username. len ( ) as u8 ;
550+ username_password_request[ 2 ..2 + username. len ( ) ] . copy_from_slice ( username. as_bytes ( ) ) ;
551+ let password_len_pos = 2 + username. len ( ) ;
552+ username_password_request[ password_len_pos] = password. len ( ) as u8 ;
553+ let password_pos = password_len_pos + 1 ;
554+ username_password_request[ password_pos..password_pos + password. len ( ) ]
555+ . copy_from_slice ( password. as_bytes ( ) ) ;
556+ let username_password_request_len = password_pos + password. len ( ) ;
557+ tcp_stream
558+ . write_all ( & username_password_request[ ..username_password_request_len] )
559+ . await
560+ . map_err ( |_| ( ) ) ?;
561+
562+ let mut username_password_reply = [ 0u8 ; USERNAME_PASSWORD_REPLY_LEN ] ;
563+ let n_read = tcp_stream. read_exact ( & mut username_password_reply) . await . map_err ( |_| ( ) ) ?;
564+ if n_read != USERNAME_PASSWORD_REPLY_LEN
565+ || username_password_reply != [ USERNAME_PASSWORD_VERSION , SUCCESS ]
566+ {
567+ return Err ( ( ) ) ;
568+ }
569+ }
570+
571+ let mut socks5_request = [ 0u8 ; SOCKS5_REQUEST_REPLY_MAX_LEN ] ;
572+ let socks5_request_len;
537573 socks5_request[ 0 ..3 ] . copy_from_slice ( & [ VERSION , CMD_CONNECT , RSV ] ) ;
538574
539575 match addr {
540576 SocketAddress :: TcpIpV4 { addr, port } => {
541577 socks5_request[ 3 ] = ATYP_IPV4 ;
542578 socks5_request[ 4 ..8 ] . copy_from_slice ( & addr) ;
543579 socks5_request[ 8 ..10 ] . copy_from_slice ( & port. to_be_bytes ( ) ) ;
544- request_size = SOCKS5_IPV4_REQUEST_LEN ;
580+ socks5_request_len = SOCKS5_IPV4_REQUEST_LEN ;
545581 } ,
546582 SocketAddress :: TcpIpV6 { addr, port } => {
547583 socks5_request[ 3 ] = ATYP_IPV6 ;
548584 socks5_request[ 4 ..20 ] . copy_from_slice ( & addr) ;
549585 socks5_request[ 20 ..22 ] . copy_from_slice ( & port. to_be_bytes ( ) ) ;
550- request_size = SOCKS5_IPV6_REQUEST_LEN ;
586+ socks5_request_len = SOCKS5_IPV6_REQUEST_LEN ;
551587 } ,
552588 ref onion_v3 @ SocketAddress :: OnionV3 { port, .. } => {
553589 let onion_v3_url = onion_v3. to_string ( ) ;
@@ -557,47 +593,46 @@ async fn socks5_connect(
557593 let port_index = 5 + hostname. len ( ) ;
558594 socks5_request[ 5 ..port_index] . copy_from_slice ( hostname) ;
559595 socks5_request[ port_index..port_index + 2 ] . copy_from_slice ( & port. to_be_bytes ( ) ) ;
560- request_size = SOCKS5_HOSTNAME_REQUEST_STATIC_FIELDS_LEN + hostname. len ( ) ;
596+ socks5_request_len = SOCKS5_HOSTNAME_REQUEST_STATIC_FIELDS_LEN + hostname. len ( ) ;
561597 } ,
562598 SocketAddress :: Hostname { hostname, port } => {
563599 socks5_request[ 3 ] = ATYP_DOMAINNAME ;
564600 socks5_request[ 4 ] = hostname. len ( ) ;
565601 let port_index = 5 + hostname. len ( ) as usize ;
566602 socks5_request[ 5 ..port_index] . copy_from_slice ( hostname. as_bytes ( ) ) ;
567603 socks5_request[ port_index..port_index + 2 ] . copy_from_slice ( & port. to_be_bytes ( ) ) ;
568- request_size = SOCKS5_HOSTNAME_REQUEST_STATIC_FIELDS_LEN + hostname. len ( ) as usize ;
604+ socks5_request_len =
605+ SOCKS5_HOSTNAME_REQUEST_STATIC_FIELDS_LEN + hostname. len ( ) as usize ;
569606 } ,
570607 SocketAddress :: OnionV2 { .. } => return Err ( ( ) ) ,
571608 } ;
572609
573- tcp_stream. write_all ( & socks5_request[ ..request_size ] ) . await . map_err ( |_| ( ) ) ?;
610+ tcp_stream. write_all ( & socks5_request[ ..socks5_request_len ] ) . await . map_err ( |_| ( ) ) ?;
574611
575612 let mut buffer = [ 0u8 ; SOCKS5_REQUEST_REPLY_MAX_LEN ] ;
576613 // First pull some bytes into the buffer
577614 let mut total_read = tcp_stream. read ( & mut buffer) . await . map_err ( |_| ( ) ) ?;
578615 // Then make sure we've reached EOF, otherwise keep appending until the max size of the buffer
579616 while total_read < SOCKS5_REQUEST_REPLY_MAX_LEN {
580617 let n_read = match tcp_stream. try_read ( & mut buffer[ total_read..] ) {
618+ Ok ( 0 ) => return Err ( ( ) ) , // The read half of the TcpStream has been closed
581619 Ok ( n) => n,
582- Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: WouldBlock => 0 ,
620+ Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: WouldBlock => break ,
583621 Err ( _e) => return Err ( ( ) ) ,
584622 } ;
585- if n_read == 0 {
586- break ;
587- }
623+
588624 total_read += n_read;
589625 }
590- // If we've filled the buffer, return `Err` if we receive more bytes than the max allowed by the RFC
626+
627+ // If we've filled the buffer and we receive more bytes, return `Err` as these bytes are not expected per the RFC
591628 if total_read == SOCKS5_REQUEST_REPLY_MAX_LEN {
592629 match tcp_stream. try_read ( & mut [ 0u8 ; 1 ] ) {
593- Ok ( 0 ) => ( ) ,
594- Ok ( 1 ..) => return Err ( ( ) ) ,
595630 Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: WouldBlock => ( ) ,
596- Err ( _e ) => return Err ( ( ) ) ,
631+ _ => return Err ( ( ) ) ,
597632 }
598633 }
599634
600- if total_read < SOCKS5_REQUEST_REPLY_MIN_LEN || buffer[ ..3 ] != [ VERSION , SUCCEEDED , RSV ] {
635+ if total_read < SOCKS5_REQUEST_REPLY_MIN_LEN || buffer[ ..3 ] != [ VERSION , SUCCESS , RSV ] {
601636 return Err ( ( ) ) ;
602637 }
603638
@@ -743,6 +778,9 @@ impl Hash for SocketDescriptor {
743778
744779#[ cfg( test) ]
745780mod tests {
781+ #[ cfg( tor_socks5) ]
782+ use super :: socks5_connect;
783+
746784 use bitcoin:: constants:: ChainHash ;
747785 use bitcoin:: secp256k1:: { PublicKey , Secp256k1 , SecretKey } ;
748786 use bitcoin:: Network ;
@@ -756,6 +794,8 @@ mod tests {
756794 use tokio:: sync:: mpsc;
757795
758796 use std:: mem;
797+ #[ cfg( tor_socks5) ]
798+ use std:: net:: SocketAddr ;
759799 use std:: sync:: atomic:: { AtomicBool , Ordering } ;
760800 use std:: sync:: { Arc , Mutex } ;
761801 use std:: time:: Duration ;
@@ -1076,4 +1116,66 @@ mod tests {
10761116 async fn unthreaded_race_disconnect_accept ( ) {
10771117 race_disconnect_accept ( ) . await ;
10781118 }
1119+
1120+ #[ cfg( tor_socks5) ]
1121+ #[ tokio:: test]
1122+ async fn test_socks5_connect ( ) {
1123+ // Set TOR_SOCKS5_PROXY=127.0.0.1:9050
1124+ let socks5_proxy_addr: SocketAddr = std:: env!( "TOR_SOCKS5_PROXY" ) . parse ( ) . unwrap ( ) ;
1125+
1126+ // Success cases
1127+
1128+ for ( addr_str, user_pass) in [
1129+ // google.com
1130+ ( "142.250.189.196:80" , None ) ,
1131+ // google.com
1132+ ( "[2607:f8b0:4005:813::2004]:80" , None ) ,
1133+ // torproject.org
1134+ ( "torproject.org:80" , None ) ,
1135+ // torproject.org
1136+ ( "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" , None ) ,
1137+ // Same vectors as above, with a username and password
1138+ ( "142.250.189.196:80" , Some ( ( "<torS0X>0" , "" ) ) ) ,
1139+ ( "[2607:f8b0:4005:813::2004]:80" , Some ( ( "<torS0X>0" , "123" ) ) ) ,
1140+ ( "torproject.org:80" , Some ( ( "<torS0X>1abc" , "" ) ) ) ,
1141+ (
1142+ "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
1143+ Some ( ( "<torS0X>1abc" , "123" ) ) ,
1144+ ) ,
1145+ ] {
1146+ let addr: SocketAddress = addr_str. parse ( ) . unwrap ( ) ;
1147+ let _tcp_stream = socks5_connect ( socks5_proxy_addr, addr, user_pass) . await . unwrap ( ) ;
1148+ }
1149+
1150+ // Failure cases
1151+
1152+ for ( addr_str, user_pass) in [
1153+ // google.com, with some invalid port
1154+ ( "142.250.189.196:1234" , None ) ,
1155+ // google.com, with some invalid port
1156+ ( "[2607:f8b0:4005:813::2004]:1234" , None ) ,
1157+ // torproject.org, with some invalid port
1158+ ( "torproject.org:1234" , None ) ,
1159+ // torproject.org, with a typo
1160+ ( "3gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" , None ) ,
1161+ // Same vectors as above, with a username and password
1162+ ( "142.250.189.196:1234" , Some ( ( "<torS0X>0" , "" ) ) ) ,
1163+ ( "[2607:f8b0:4005:813::2004]:1234" , Some ( ( "<torS0X>0" , "123" ) ) ) ,
1164+ ( "torproject.org:1234" , Some ( ( "<torS0X>1abc" , "" ) ) ) ,
1165+ (
1166+ "3gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80" ,
1167+ Some ( ( "<torS0X>1abc" , "123" ) ) ,
1168+ ) ,
1169+ /* TODO: Uncomment when format types 30 and 31 land in tor stable, see https://spec.torproject.org/socks-extensions.html,
1170+ these are invalid usernames according to those standards.
1171+ ("142.250.189.196:80", Some(("<torS0X>0abc", "123"))),
1172+ ("[2607:f8b0:4005:813::2004]:80", Some(("<torS0X>1", "123"))),
1173+ ("torproject.org:80", Some(("<torS0X>9", "123"))),
1174+ ("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80", Some(("<torS0X>", "123"))),
1175+ */
1176+ ] {
1177+ let addr: SocketAddress = addr_str. parse ( ) . unwrap ( ) ;
1178+ assert ! ( socks5_connect( socks5_proxy_addr, addr, user_pass) . await . is_err( ) ) ;
1179+ }
1180+ }
10791181}
0 commit comments