@@ -13,12 +13,17 @@ use std::time::Duration;
1313
1414use bitcoin:: secp256k1:: PublicKey ;
1515use lightning:: ln:: msgs:: SocketAddress ;
16- use lightning:: sign:: RandomBytes ;
16+ use lightning:: sign:: { EntropySource , RandomBytes } ;
17+
18+ use tokio:: io:: { AsyncReadExt , AsyncWriteExt } ;
19+ use tokio:: net:: TcpStream ;
1720
1821use crate :: logger:: { log_error, log_info, LdkLogger } ;
1922use crate :: types:: PeerManager ;
2023use crate :: Error ;
2124
25+ const TOR_CONNECT_OUTBOUND_TIMEOUT : u64 = 30 ;
26+
2227pub ( crate ) struct ConnectionManager < L : Deref + Clone + Sync + Send >
2328where
2429 L :: Target : LdkLogger ,
@@ -90,13 +95,27 @@ where
9095 self . propagate_result_to_subscribers ( & node_id, Err ( Error :: InvalidSocketAddress ) ) ;
9196 Error :: InvalidSocketAddress
9297 } ) ?;
93- let connection_future = lightning_net_tokio:: tor_connect_outbound (
94- Arc :: clone ( & self . peer_manager ) ,
95- node_id,
96- addr. clone ( ) ,
97- proxy_addr,
98- self . tor_proxy_rng . clone ( ) ,
99- ) ;
98+ let rng = self . tor_proxy_rng . clone ( ) ;
99+ let pm = Arc :: clone ( & self . peer_manager ) ;
100+ let addr_clone = addr. clone ( ) ;
101+ let connection_future = async move {
102+ let connect_fut = async {
103+ tor_socks5_connect ( addr_clone. clone ( ) , proxy_addr, & * rng)
104+ . await
105+ . map ( |s| s. into_std ( ) . unwrap ( ) )
106+ } ;
107+ match tokio:: time:: timeout (
108+ Duration :: from_secs ( TOR_CONNECT_OUTBOUND_TIMEOUT ) ,
109+ connect_fut,
110+ )
111+ . await
112+ {
113+ Ok ( Ok ( stream) ) => {
114+ Some ( lightning_net_tokio:: setup_outbound ( pm, node_id, stream) )
115+ } ,
116+ _ => None ,
117+ }
118+ } ;
100119 self . await_connection ( connection_future, node_id, addr) . await
101120 } else {
102121 let socket_addr = addr
@@ -201,3 +220,173 @@ where
201220 }
202221 }
203222}
223+
224+ /// Connect to a peer's SocketAddress through a Tor SOCKS5 proxy.
225+ /// Uses Tor stream isolation via username/password auth (RFC 1929 + Tor extensions).
226+ async fn tor_socks5_connect < ES : Deref > (
227+ addr : SocketAddress , tor_proxy_addr : core:: net:: SocketAddr , entropy_source : ES ,
228+ ) -> Result < TcpStream , ( ) >
229+ where
230+ ES :: Target : EntropySource ,
231+ {
232+ use std:: io:: Write ;
233+
234+ // SOCKS5 constants (RFC 1928 / RFC 1929)
235+ const VERSION : u8 = 5 ;
236+ const NMETHODS : u8 = 1 ;
237+ const USERNAME_PASSWORD_AUTH : u8 = 2 ;
238+ const METHOD_SELECT_REPLY_LEN : usize = 2 ;
239+ const USERNAME_PASSWORD_VERSION : u8 = 1 ;
240+ const USERNAME_PASSWORD_REPLY_LEN : usize = 2 ;
241+ const CMD_CONNECT : u8 = 1 ;
242+ const RSV : u8 = 0 ;
243+ const ATYP_DOMAINNAME : u8 = 3 ;
244+ const ATYP_IPV4 : u8 = 1 ;
245+ const ATYP_IPV6 : u8 = 4 ;
246+ const SUCCESS : u8 = 0 ;
247+
248+ // Tor extensions for stream isolation
249+ const USERNAME : & [ u8 ] = b"<torS0X>0" ;
250+ const USERNAME_LEN : usize = USERNAME . len ( ) ;
251+ const PASSWORD_ENTROPY_LEN : usize = 32 ;
252+ const PASSWORD_LEN : usize = PASSWORD_ENTROPY_LEN * 2 ; // hex-encoded
253+
254+ const IPV4_ADDR_LEN : usize = 4 ;
255+ const IPV6_ADDR_LEN : usize = 16 ;
256+ const HOSTNAME_MAX_LEN : usize = u8:: MAX as usize ;
257+
258+ const USERNAME_PASSWORD_REQUEST_LEN : usize =
259+ 1 + 1 + USERNAME_LEN + 1 + PASSWORD_LEN ;
260+ const SOCKS5_REQUEST_MAX_LEN : usize =
261+ 1 + 1 + 1 + 1 + 1 + HOSTNAME_MAX_LEN + 2 ;
262+ const SOCKS5_REPLY_HEADER_LEN : usize = 4 ; // VER + REP + RSV + ATYP
263+
264+ // Step 1: Connect to the SOCKS5 proxy
265+ let mut tcp_stream = TcpStream :: connect ( & tor_proxy_addr) . await . map_err ( |_| ( ) ) ?;
266+
267+ // Step 2: Method selection — request username/password auth
268+ let method_selection_request = [ VERSION , NMETHODS , USERNAME_PASSWORD_AUTH ] ;
269+ tcp_stream. write_all ( & method_selection_request) . await . map_err ( |_| ( ) ) ?;
270+
271+ let mut method_selection_reply = [ 0u8 ; METHOD_SELECT_REPLY_LEN ] ;
272+ tcp_stream. read_exact ( & mut method_selection_reply) . await . map_err ( |_| ( ) ) ?;
273+ if method_selection_reply != [ VERSION , USERNAME_PASSWORD_AUTH ] {
274+ return Err ( ( ) ) ;
275+ }
276+
277+ // Step 3: Authenticate with random password for Tor stream isolation
278+ let password: [ u8 ; PASSWORD_ENTROPY_LEN ] = entropy_source. get_secure_random_bytes ( ) ;
279+ let mut username_password_request = [ 0u8 ; USERNAME_PASSWORD_REQUEST_LEN ] ;
280+ {
281+ let mut stream = & mut username_password_request[ ..] ;
282+ stream. write_all ( & [ USERNAME_PASSWORD_VERSION , USERNAME_LEN as u8 ] ) . unwrap ( ) ;
283+ stream. write_all ( USERNAME ) . unwrap ( ) ;
284+ stream. write_all ( & [ PASSWORD_LEN as u8 ] ) . unwrap ( ) ;
285+ for byte in password {
286+ write ! ( stream, "{:02x}" , byte) . unwrap ( ) ;
287+ }
288+ }
289+ tcp_stream. write_all ( & username_password_request) . await . map_err ( |_| ( ) ) ?;
290+
291+ let mut auth_reply = [ 0u8 ; USERNAME_PASSWORD_REPLY_LEN ] ;
292+ tcp_stream. read_exact ( & mut auth_reply) . await . map_err ( |_| ( ) ) ?;
293+ if auth_reply[ 1 ] != SUCCESS {
294+ return Err ( ( ) ) ;
295+ }
296+
297+ // Step 4: Send CONNECT request for the target address
298+ let mut socks5_request = [ 0u8 ; SOCKS5_REQUEST_MAX_LEN ] ;
299+ let request_len = {
300+ let mut stream = & mut socks5_request[ ..] ;
301+ stream. write_all ( & [ VERSION , CMD_CONNECT , RSV ] ) . unwrap ( ) ;
302+
303+ match & addr {
304+ SocketAddress :: OnionV3 { ed25519_pubkey, checksum, version, port } => {
305+ // Encode as domain name (base32 .onion hostname)
306+ // OnionV3 address = base32(pubkey[32] || checksum[2] || version[1]) + ".onion"
307+ let mut raw = Vec :: with_capacity ( 35 ) ;
308+ raw. extend_from_slice ( ed25519_pubkey) ;
309+ raw. push ( ( checksum >> 8 ) as u8 ) ;
310+ raw. push ( * checksum as u8 ) ;
311+ raw. push ( * version) ;
312+ let encoded = base32_encode_lowercase ( & raw ) ;
313+ let mut onion_host = encoded. into_bytes ( ) ;
314+ onion_host. extend_from_slice ( b".onion" ) ;
315+
316+ stream. write_all ( & [ ATYP_DOMAINNAME , onion_host. len ( ) as u8 ] ) . unwrap ( ) ;
317+ stream. write_all ( & onion_host) . unwrap ( ) ;
318+ stream. write_all ( & port. to_be_bytes ( ) ) . unwrap ( ) ;
319+ } ,
320+ SocketAddress :: TcpIpV4 { addr : ip, port } => {
321+ stream. write_all ( & [ ATYP_IPV4 ] ) . unwrap ( ) ;
322+ stream. write_all ( ip) . unwrap ( ) ;
323+ stream. write_all ( & port. to_be_bytes ( ) ) . unwrap ( ) ;
324+ } ,
325+ SocketAddress :: TcpIpV6 { addr : ip, port } => {
326+ stream. write_all ( & [ ATYP_IPV6 ] ) . unwrap ( ) ;
327+ stream. write_all ( ip) . unwrap ( ) ;
328+ stream. write_all ( & port. to_be_bytes ( ) ) . unwrap ( ) ;
329+ } ,
330+ SocketAddress :: Hostname { hostname, port } => {
331+ let host_str = hostname. to_string ( ) ;
332+ let host_bytes = host_str. as_bytes ( ) ;
333+ stream. write_all ( & [ ATYP_DOMAINNAME , host_bytes. len ( ) as u8 ] ) . unwrap ( ) ;
334+ stream. write_all ( host_bytes) . unwrap ( ) ;
335+ stream. write_all ( & port. to_be_bytes ( ) ) . unwrap ( ) ;
336+ } ,
337+ _ => return Err ( ( ) ) ,
338+ }
339+
340+ SOCKS5_REQUEST_MAX_LEN - stream. len ( )
341+ } ;
342+
343+ tcp_stream. write_all ( & socks5_request[ ..request_len] ) . await . map_err ( |_| ( ) ) ?;
344+
345+ // Step 5: Read SOCKS5 reply
346+ let mut reply_header = [ 0u8 ; SOCKS5_REPLY_HEADER_LEN ] ;
347+ tcp_stream. read_exact ( & mut reply_header) . await . map_err ( |_| ( ) ) ?;
348+
349+ if reply_header[ 1 ] != SUCCESS {
350+ return Err ( ( ) ) ;
351+ }
352+
353+ // Consume the bound address from the reply
354+ let addr_len = match reply_header[ 3 ] {
355+ ATYP_IPV4 => IPV4_ADDR_LEN + 2 ,
356+ ATYP_IPV6 => IPV6_ADDR_LEN + 2 ,
357+ ATYP_DOMAINNAME => {
358+ let mut len_buf = [ 0u8 ; 1 ] ;
359+ tcp_stream. read_exact ( & mut len_buf) . await . map_err ( |_| ( ) ) ?;
360+ len_buf[ 0 ] as usize + 2
361+ } ,
362+ _ => return Err ( ( ) ) ,
363+ } ;
364+ let mut addr_buf = vec ! [ 0u8 ; addr_len] ;
365+ tcp_stream. read_exact ( & mut addr_buf) . await . map_err ( |_| ( ) ) ?;
366+
367+ Ok ( tcp_stream)
368+ }
369+
370+ /// RFC 4648 base32 encoding (lowercase, no padding) for onion v3 address derivation.
371+ fn base32_encode_lowercase ( data : & [ u8 ] ) -> String {
372+ const ALPHABET : & [ u8 ] = b"abcdefghijklmnopqrstuvwxyz234567" ;
373+ let mut result = String :: with_capacity ( ( data. len ( ) * 8 + 4 ) / 5 ) ;
374+ let mut buffer: u64 = 0 ;
375+ let mut bits_left = 0 ;
376+
377+ for & byte in data {
378+ buffer = ( buffer << 8 ) | byte as u64 ;
379+ bits_left += 8 ;
380+ while bits_left >= 5 {
381+ bits_left -= 5 ;
382+ result. push ( ALPHABET [ ( ( buffer >> bits_left) & 0x1f ) as usize ] as char ) ;
383+ }
384+ }
385+
386+ if bits_left > 0 {
387+ buffer <<= 5 - bits_left;
388+ result. push ( ALPHABET [ ( buffer & 0x1f ) as usize ] as char ) ;
389+ }
390+
391+ result
392+ }
0 commit comments