Skip to content

Commit 8d22deb

Browse files
committed
fixup: enable user/password auth, add tests
1 parent e278a1b commit 8d22deb

File tree

2 files changed

+130
-27
lines changed

2 files changed

+130
-27
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ check-cfg = [
6767
"cfg(require_route_graph_test)",
6868
"cfg(simple_close)",
6969
"cfg(peer_storage)",
70+
"cfg(tor_socks5)",
7071
]

lightning-net-tokio/src/lib.rs

Lines changed: 129 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -477,12 +477,14 @@ where
477477
/// Same as [`connect_outbound`], using a SOCKS5 proxy
478478
pub 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 = ()>>
481482
where
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

495497
async 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)]
745780
mod 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

Comments
 (0)