Skip to content

Commit e5f6359

Browse files
committed
net-tokio: add fn socks5_connect_outbound
1 parent c722443 commit e5f6359

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

lightning-net-tokio/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ rustdoc-args = ["--cfg", "docsrs"]
1919
[dependencies]
2020
bitcoin = "0.32.2"
2121
lightning = { version = "0.3.0", path = "../lightning" }
22-
tokio = { version = "1.35", features = [ "rt", "sync", "net", "time" ] }
22+
tokio = { version = "1.35", features = [ "rt", "sync", "net", "time", "io-util" ] }
2323

2424
[dev-dependencies]
2525
tokio = { version = "1.35", features = [ "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }

lightning-net-tokio/src/lib.rs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ use std::time::Duration;
5151

5252
static ID_COUNTER: AtomicU64 = AtomicU64::new(0);
5353

54+
const CONNECT_OUTBOUND_TIMEOUT: u64 = 10;
55+
const SOCKS5_CONNECT_OUTBOUND_TIMEOUT: u64 = 30;
56+
5457
// We only need to select over multiple futures in one place, and taking on the full `tokio/macros`
5558
// dependency tree in order to do so (which has broken our MSRV before) is excessive. Instead, we
5659
// define a trivial two- and three- select macro with the specific types we need and just use that.
@@ -462,13 +465,114 @@ where
462465
PM::Target: APeerManager<Descriptor = SocketDescriptor>,
463466
{
464467
let connect_fut = async { TcpStream::connect(&addr).await.map(|s| s.into_std().unwrap()) };
465-
if let Ok(Ok(stream)) = time::timeout(Duration::from_secs(10), connect_fut).await {
468+
if let Ok(Ok(stream)) =
469+
time::timeout(Duration::from_secs(CONNECT_OUTBOUND_TIMEOUT), connect_fut).await
470+
{
466471
Some(setup_outbound(peer_manager, their_node_id, stream))
467472
} else {
468473
None
469474
}
470475
}
471476

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+
) -> Option<impl std::future::Future<Output = ()>>
481+
where
482+
PM::Target: APeerManager<Descriptor = SocketDescriptor>,
483+
{
484+
let connect_fut =
485+
async { socks5_connect(socks5_proxy_addr, addr).await.map(|s| s.into_std().unwrap()) };
486+
if let Ok(Ok(stream)) =
487+
time::timeout(Duration::from_secs(SOCKS5_CONNECT_OUTBOUND_TIMEOUT), connect_fut).await
488+
{
489+
Some(setup_outbound(peer_manager, their_node_id, stream))
490+
} else {
491+
None
492+
}
493+
}
494+
495+
async fn socks5_connect(
496+
socks5_proxy_addr: SocketAddr, addr: SocketAddress,
497+
) -> Result<TcpStream, ()> {
498+
use tokio::io::AsyncReadExt;
499+
use tokio::io::AsyncWriteExt;
500+
501+
// Constants defined in RFC 1928
502+
const VERSION: u8 = 5;
503+
const NMETHODS: u8 = 1;
504+
const NO_AUTH: u8 = 0;
505+
const METHOD_SELECT_RES_LEN: usize = 2;
506+
const CMD_CONNECT: u8 = 1;
507+
const RSV: u8 = 0;
508+
const ATYP_IPV4: u8 = 1;
509+
const ATYP_DOMAINNAME: u8 = 3;
510+
const ATYP_IPV6: u8 = 4;
511+
const SUCCEEDED: u8 = 0;
512+
513+
const SOCKS5_REQUEST_REPLY_MAX_LEN: usize = 1 /* VER */ + 1 /* CMD for request, REP for reply */ + 1 /* RSV */
514+
+ 1 /* ATYP */ + 1 /* HOSTNAME len */ + 255 /* HOSTNAME */ + 2 /* PORT */;
515+
const SOCKS5_REQUEST_REPLY_MIN_LEN: usize = 1 /* VER */ + 1 /* CMD for request, REP for reply */ + 1 /* RSV */
516+
+ 1 /* ATYP */ + 4 /* IPV4 ADDR */ + 2 /* PORT */;
517+
518+
let method_selection_message = [VERSION, NMETHODS, NO_AUTH];
519+
let mut tcp_stream = TcpStream::connect(&socks5_proxy_addr).await.map_err(|_| ())?;
520+
tcp_stream.write_all(&method_selection_message).await.map_err(|_| ())?;
521+
522+
let mut method_selection_response = [0u8; METHOD_SELECT_RES_LEN];
523+
let n_read = tcp_stream.read_exact(&mut method_selection_response).await.map_err(|_| ())?;
524+
if n_read != METHOD_SELECT_RES_LEN || method_selection_response != [VERSION, NO_AUTH] {
525+
return Err(());
526+
}
527+
528+
let mut socks5_request: Vec<u8> = Vec::with_capacity(SOCKS5_REQUEST_REPLY_MAX_LEN);
529+
530+
socks5_request.push(VERSION);
531+
socks5_request.push(CMD_CONNECT);
532+
socks5_request.push(RSV);
533+
534+
match addr {
535+
SocketAddress::TcpIpV4 { addr, port } => {
536+
socks5_request.push(ATYP_IPV4);
537+
socks5_request.extend_from_slice(&addr);
538+
socks5_request.extend_from_slice(&port.to_be_bytes());
539+
},
540+
SocketAddress::TcpIpV6 { addr, port } => {
541+
socks5_request.push(ATYP_IPV6);
542+
socks5_request.extend_from_slice(&addr);
543+
socks5_request.extend_from_slice(&port.to_be_bytes());
544+
},
545+
ref onion_v3 @ SocketAddress::OnionV3 { port, .. } => {
546+
let onion_v3_url = onion_v3.to_string();
547+
let hostname = onion_v3_url.split_once(':').ok_or(())?.0.as_bytes();
548+
socks5_request.push(ATYP_DOMAINNAME);
549+
socks5_request.extend_from_slice(&[hostname.len() as u8]);
550+
socks5_request.extend_from_slice(hostname);
551+
socks5_request.extend_from_slice(&port.to_be_bytes());
552+
},
553+
SocketAddress::Hostname { hostname, port } => {
554+
socks5_request.push(ATYP_DOMAINNAME);
555+
socks5_request.extend_from_slice(&[hostname.len() as u8]);
556+
socks5_request.extend_from_slice(hostname.as_bytes());
557+
socks5_request.extend_from_slice(&port.to_be_bytes());
558+
},
559+
SocketAddress::OnionV2 { .. } => return Err(()),
560+
};
561+
562+
tcp_stream.write_all(&socks5_request).await.map_err(|_| ())?;
563+
564+
let mut buffer: Vec<u8> = Vec::with_capacity(SOCKS5_REQUEST_REPLY_MAX_LEN);
565+
let n_read = tcp_stream.read_to_end(&mut buffer).await.map_err(|_| ())?;
566+
if n_read < SOCKS5_REQUEST_REPLY_MIN_LEN
567+
|| n_read > SOCKS5_REQUEST_REPLY_MAX_LEN
568+
|| buffer[..3] != [VERSION, SUCCEEDED, RSV]
569+
{
570+
return Err(());
571+
}
572+
573+
Ok(tcp_stream)
574+
}
575+
472576
const SOCK_WAKER_VTABLE: task::RawWakerVTable = task::RawWakerVTable::new(
473577
clone_socket_waker,
474578
wake_socket_waker,

0 commit comments

Comments
 (0)