Skip to content

Commit 051b8f2

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 051b8f2

File tree

3 files changed

+86
-82
lines changed

3 files changed

+86
-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 using lightning-net-tokio
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: 69 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,18 @@ 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+
pub async fn tor_connect_outbound<PM: Deref + 'static + Send + Sync + Clone, ES: Deref>(
481+
peer_manager: PM, their_node_id: PublicKey, addr: SocketAddress, tor_proxy_addr: SocketAddr,
482+
entropy_source: ES,
481483
) -> Option<impl std::future::Future<Output = ()>>
482484
where
483485
PM::Target: APeerManager<Descriptor = SocketDescriptor>,
486+
ES::Target: EntropySource,
484487
{
485488
let connect_fut = async {
486-
socks5_connect(socks5_proxy_addr, addr, user_pass).await.map(|s| s.into_std().unwrap())
489+
tor_connect(addr, tor_proxy_addr, entropy_source).await.map(|s| s.into_std().unwrap())
487490
};
488491
if let Ok(Ok(stream)) =
489492
time::timeout(Duration::from_secs(SOCKS5_CONNECT_OUTBOUND_TIMEOUT), connect_fut).await
@@ -494,9 +497,12 @@ where
494497
}
495498
}
496499

497-
async fn socks5_connect(
498-
socks5_proxy_addr: SocketAddr, addr: SocketAddress, user_pass: Option<(&str, &str)>,
499-
) -> Result<TcpStream, ()> {
500+
async fn tor_connect<ES: Deref>(
501+
addr: SocketAddress, tor_proxy_addr: SocketAddr, entropy_source: ES,
502+
) -> Result<TcpStream, ()>
503+
where
504+
ES::Target: EntropySource,
505+
{
500506
use std::io::{Cursor, Write};
501507
use tokio::io::AsyncReadExt;
502508

@@ -507,7 +513,6 @@ async fn socks5_connect(
507513
// Constants defined in RFC 1928 and RFC 1929
508514
const VERSION: u8 = 5;
509515
const NMETHODS: u8 = 1;
510-
const NO_AUTH: u8 = 0;
511516
const USERNAME_PASSWORD_AUTH: u8 = 2;
512517
const METHOD_SELECT_REPLY_LEN: usize = 2;
513518
const USERNAME_PASSWORD_VERSION: u8 = 1;
@@ -518,48 +523,45 @@ async fn socks5_connect(
518523
const ATYP_DOMAINNAME: u8 = 3;
519524
const ATYP_IPV6: u8 = 4;
520525
const SUCCESS: u8 = 0;
521-
const USERNAME_MAX_LEN: usize = 255;
522-
const PASSWORD_MAX_LEN: usize = 255;
523526

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 */;
527+
// Tor extensions, see https://spec.torproject.org/socks-extensions.html for further details
528+
const USERNAME: &[u8] = b"<torS0X>0";
529+
const USERNAME_LEN: usize = USERNAME.len();
530+
const PASSWORD_LEN: usize = 32;
531+
532+
const USERNAME_PASSWORD_REQUEST_LEN: usize =
533+
1 /* VER */ + 1 /* ULEN */ + USERNAME_LEN + 1 /* PLEN */ + PASSWORD_LEN;
526534
const SOCKS5_REQUEST_MAX_LEN: usize = 1 /* VER */ + 1 /* CMD */ + 1 /* RSV */ + 1 /* ATYP */
527535
+ 1 /* HOSTNAME len */ + HOSTNAME_MAX_LEN /* HOSTNAME */ + 2 /* PORT */;
528536

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(|_| ())?;
537+
let method_selection_request = [VERSION, NMETHODS, USERNAME_PASSWORD_AUTH];
538+
let mut tcp_stream = TcpStream::connect(&tor_proxy_addr).await.map_err(|_| ())?;
532539
tokio::io::AsyncWriteExt::write_all(&mut tcp_stream, &method_selection_request)
533540
.await
534541
.map_err(|_| ())?;
535542

536543
let mut method_selection_reply = [0u8; METHOD_SELECT_REPLY_LEN];
537544
tcp_stream.read_exact(&mut method_selection_reply).await.map_err(|_| ())?;
538-
if method_selection_reply != [VERSION, selected_auth] {
545+
if method_selection_reply != [VERSION, USERNAME_PASSWORD_AUTH] {
539546
return Err(());
540547
}
541548

542-
if let Some((username, password)) = user_pass {
543-
if username.len() > USERNAME_MAX_LEN || password.len() > PASSWORD_MAX_LEN {
544-
return Err(());
545-
}
549+
let password: [u8; PASSWORD_LEN] = entropy_source.get_secure_random_bytes();
550+
let mut username_password_request = [0u8; USERNAME_PASSWORD_REQUEST_LEN];
551+
let mut writer = Cursor::new(&mut username_password_request[..]);
552+
writer.write_all(&[USERNAME_PASSWORD_VERSION, USERNAME_LEN as u8]).map_err(|_| ())?;
553+
writer.write_all(USERNAME).map_err(|_| ())?;
554+
writer.write_all(&[PASSWORD_LEN as u8]).map_err(|_| ())?;
555+
writer.write_all(&password).map_err(|_| ())?;
556+
debug_assert_eq!(writer.position() as usize, USERNAME_PASSWORD_REQUEST_LEN);
557+
tokio::io::AsyncWriteExt::write_all(&mut tcp_stream, &username_password_request)
558+
.await
559+
.map_err(|_| ())?;
546560

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-
}
561+
let mut username_password_reply = [0u8; USERNAME_PASSWORD_REPLY_LEN];
562+
tcp_stream.read_exact(&mut username_password_reply).await.map_err(|_| ())?;
563+
if username_password_reply != [USERNAME_PASSWORD_VERSION, SUCCESS] {
564+
return Err(());
563565
}
564566

565567
let mut socks5_request = [0u8; SOCKS5_REQUEST_MAX_LEN];
@@ -754,9 +756,6 @@ impl Hash for SocketDescriptor {
754756

755757
#[cfg(test)]
756758
mod tests {
757-
#[cfg(tor_socks5)]
758-
use super::socks5_connect;
759-
760759
use bitcoin::constants::ChainHash;
761760
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
762761
use bitcoin::Network;
@@ -770,8 +769,6 @@ mod tests {
770769
use tokio::sync::mpsc;
771770

772771
use std::mem;
773-
#[cfg(tor_socks5)]
774-
use std::net::SocketAddr;
775772
use std::sync::atomic::{AtomicBool, Ordering};
776773
use std::sync::{Arc, Mutex};
777774
use std::time::Duration;
@@ -1093,34 +1090,40 @@ mod tests {
10931090
race_disconnect_accept().await;
10941091
}
10951092

1096-
#[cfg(tor_socks5)]
1093+
#[cfg(tor)]
10971094
#[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();
1095+
async fn test_tor_connect() {
1096+
use super::tor_connect;
1097+
use lightning::sign::EntropySource;
1098+
use std::net::SocketAddr;
1099+
1100+
// Set TOR_PROXY=127.0.0.1:9050
1101+
let tor_proxy_addr: SocketAddr = std::env!("TOR_PROXY").parse().unwrap();
1102+
1103+
struct TestEntropySource;
1104+
1105+
impl EntropySource for TestEntropySource {
1106+
fn get_secure_random_bytes(&self) -> [u8; 32] {
1107+
[0xffu8; 32]
1108+
}
1109+
}
1110+
1111+
let entropy_source = TestEntropySource;
11011112

11021113
// Success cases
11031114

1104-
for (addr_str, user_pass) in [
1115+
for addr_str in [
11051116
// google.com
1106-
("142.250.189.196:80", None),
1117+
"142.250.189.196:80",
11071118
// google.com
1108-
("[2607:f8b0:4005:813::2004]:80", None),
1119+
"[2607:f8b0:4005:813::2004]:80",
11091120
// torproject.org
1110-
("torproject.org:80", None),
1121+
"torproject.org:80",
11111122
// 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-
),
1123+
"2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80",
11211124
] {
11221125
let addr: SocketAddress = addr_str.parse().unwrap();
1123-
let tcp_stream = socks5_connect(socks5_proxy_addr, addr, user_pass).await.unwrap();
1126+
let tcp_stream = tor_connect(addr, tor_proxy_addr, &entropy_source).await.unwrap();
11241127
assert_eq!(
11251128
tcp_stream.try_read(&mut [0u8; 1]).unwrap_err().kind(),
11261129
std::io::ErrorKind::WouldBlock
@@ -1129,33 +1132,18 @@ mod tests {
11291132

11301133
// Failure cases
11311134

1132-
for (addr_str, user_pass) in [
1135+
for addr_str in [
11331136
// google.com, with some invalid port
1134-
("142.250.189.196:1234", None),
1137+
"142.250.189.196:1234",
11351138
// google.com, with some invalid port
1136-
("[2607:f8b0:4005:813::2004]:1234", None),
1139+
"[2607:f8b0:4005:813::2004]:1234",
11371140
// torproject.org, with some invalid port
1138-
("torproject.org:1234", None),
1141+
"torproject.org:1234",
11391142
// 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-
*/
1143+
"3gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion:80",
11561144
] {
11571145
let addr: SocketAddress = addr_str.parse().unwrap();
1158-
assert!(socks5_connect(socks5_proxy_addr, addr, user_pass).await.is_err());
1146+
assert!(tor_connect(addr, tor_proxy_addr, &entropy_source).await.is_err());
11591147
}
11601148
}
11611149
}

0 commit comments

Comments
 (0)