Skip to content

Commit a04440d

Browse files
committed
fixup: Add CI, hardcode the username to <torS0X>0, and...
source the stream isolation parameter from `EntropySource::get_secure_random_bytes`
1 parent 323f1e0 commit a04440d

File tree

3 files changed

+89
-82
lines changed

3 files changed

+89
-82
lines changed

.github/workflows/build.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,19 @@ jobs:
320320
run: cargo fmt --check
321321
- name: Run rustfmt checks on lightning-tests
322322
run: cd lightning-tests && cargo fmt --check
323+
tor-connect:
324+
runs-on: ubuntu-latest
325+
env:
326+
TOOLCHAIN: 1.75.0
327+
steps:
328+
- name: Checkout source code
329+
uses: actions/checkout@v4
330+
- name: Install tor
331+
run: |
332+
sudo apt install -y tor
333+
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
334+
run: |
335+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }}
336+
- name: Test tor connections
337+
run: |
338+
TOR_PROXY="127.0.0.1:9050" RUSTFLAGS="--cfg=tor" cargo test --verbose --color always -p lightning-net-tokio

Cargo.toml

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

lightning-net-tokio/src/lib.rs

Lines changed: 72 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use lightning::ln::msgs::SocketAddress;
3737
use lightning::ln::peer_handler;
3838
use lightning::ln::peer_handler::APeerManager;
3939
use lightning::ln::peer_handler::SocketDescriptor as LnSocketTrait;
40+
use lightning::sign::EntropySource;
4041

4142
use std::future::Future;
4243
use 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 = ()>>
482485
where
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)]
756761
mod 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

Comments
 (0)