From 51381b3e5e07410d061c207c9dec729a9ecb242c Mon Sep 17 00:00:00 2001 From: Jack Wampler Date: Wed, 15 May 2024 13:37:48 -0600 Subject: [PATCH] Establish an MSRV and add a CI test to ensure compatibility (#44) adding CI tests for msrv --- .github/workflows/rust.yml | 19 +++++++++++ Cargo.toml | 2 +- README.md | 19 +++++++---- crates/lyrebird/Cargo.toml | 4 +-- crates/lyrebird/src/fwd/backend.rs | 33 +++++++++++++------ crates/lyrebird/src/fwd/main.rs | 2 +- crates/lyrebird/src/main.rs | 4 +-- crates/o5/Cargo.toml | 2 +- crates/o7/Cargo.toml | 2 +- crates/obfs4/Cargo.toml | 19 +++++------ crates/obfs4/src/sessions.rs | 4 +-- crates/obfs4/src/testing.rs | 51 +++++++++++++++--------------- crates/ptrs/Cargo.toml | 4 +-- crates/ptrs/src/helpers.rs | 36 +++++++++++++++------ crates/ptrs/src/passthrough.rs | 3 +- 15 files changed, 132 insertions(+), 72 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 824939c..0f63884 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -94,6 +94,25 @@ jobs: - uses: dtolnay/rust-toolchain@nightly - run: cargo test --workspace --all-targets + msrv: + name: Check Crates against MSRV + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + # Re-resolve Cargo.lock with minimal versions + - uses: dtolnay/rust-toolchain@nightly + - run: cargo update -Z minimal-versions + # Now check that `cargo build` works with respect to the oldest possible + # deps and the stated MSRV. 1.70 should work for all + - uses: dtolnay/rust-toolchain@1.70.0 + - run: cargo test --workspace --all-targets --all-features + # Also make sure the AVX2 build works + - run: cargo build --target x86_64-unknown-linux-gnu + # The PTRS crate has fewer dependencies and a lower msrv + - uses: dtolnay/rust-toolchain@1.63 + - run: cargo test -p ptrs --all-targets --all-features + - run: cargo build -p ptrs --target x86_64-unknown-linux-gnu + build: name: Build needs: format diff --git a/Cargo.toml b/Cargo.toml index 3cbffb0..575115f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "crates/lyrebird", - "crates/o5", + # "crates/o5", "crates/o7", "crates/obfs4", "crates/ptrs", diff --git a/README.md b/README.md index 635759d..883f500 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,23 @@ This repository contains multiple related crates implementing the lyrebird (obfs lyrebird binary, and Pluggable Transports in Rust (PTRS) library. -| Crate | Description | Crates.io | Docs | --------------------------------------------|----------------|-----------|------| -| [`ptrs`](./crates/ptrs) | A library supporting implementation and integration of Pluggable Transport protocols. | [![](https://img.shields.io/crates/v/ptrs.svg)](https://crates.io/crates/ptrs) | [![](https://img.shields.io/docsrs/ptrs)](https://docs.rs/ptrs) | -| [`lyrebird`](./crates/lyrebird) | Implementation of the `Lyrebird` Tor bridge and a forward proxy compatible with `ptrs`. | [![](https://img.shields.io/crates/v/lyrebird.svg)](https://crates.io/crates/lyrebird) | [![](https://docs.rs/lyrebird/badge.svg)](https://docs.rs/lyrebird) | -| [`obfs4`](./crates/obfs4) | An implementation of obfs4 pluggable transport library in pure rust. | [![](https://img.shields.io/crates/v/obfs4.svg)](https://crates.io/crates/obfs4) | [![](https://docs.rs/obfs4/badge.svg)](https://docs.rs/obfs4) | +| Crate | Description | Crates.io | Docs | MSRV | +-------------------------------------------|----------------|-----------|------|------| +| [`ptrs`](./crates/ptrs) | A library supporting implementation and integration of Pluggable Transport protocols. | [![](https://img.shields.io/crates/v/ptrs.svg)](https://crates.io/crates/ptrs) | [![](https://img.shields.io/docsrs/ptrs)](https://docs.rs/ptrs) | 1.63 | +| [`lyrebird`](./crates/lyrebird) | Implementation of the `Lyrebird` Tor bridge and a forward proxy compatible with `ptrs`. | [![](https://img.shields.io/crates/v/lyrebird.svg)](https://crates.io/crates/lyrebird) | [![](https://docs.rs/lyrebird/badge.svg)](https://docs.rs/lyrebird) | 1.70 | +| [`obfs4`](./crates/obfs4) | An implementation of obfs4 pluggable transport library in pure rust. | [![](https://img.shields.io/crates/v/obfs4.svg)](https://crates.io/crates/obfs4) | [![](https://docs.rs/obfs4/badge.svg)](https://docs.rs/obfs4) | 1.70 | ## MSRV -The planned Minimum Supported Rust Version (MSRV) is 1.60, however there is no -current testing to ensure that this is working currently. +The Minimum Supported Rust Versions (MSRV) for the various crates are listed above. +These are ensured by test and build steps in the CI pipeline. MSRV can be changed in the future, but it will be done with a minor version bump. +We will not increase MSRV on PATCH releases, though downstream dependencies might. + +We won't increase MSRV just because we can: we'll only do so when we have a +reason. (We don't guarantee that you'll agree with our reasoning; only that +it will exist.) ## Related diff --git a/crates/lyrebird/Cargo.toml b/crates/lyrebird/Cargo.toml index 571ad05..eec093b 100644 --- a/crates/lyrebird/Cargo.toml +++ b/crates/lyrebird/Cargo.toml @@ -35,12 +35,12 @@ obfs4 = { path="../obfs4", version="0.1.0" } # fwd deps anyhow = "1.0" -clap = { version = "4.4.7", features = ["derive"]} +clap = { version = "4.4", features = ["derive"]} fast-socks5 = "0.9.1" futures = "0.3.29" safelog = "0.3.5" thiserror = "1.0.56" -tokio = { version = "1.33", features = ["io-util", "net", "macros", "sync", "signal"] } +tokio = { version = "1.34", features = ["io-util", "net", "macros", "sync", "signal"] } tokio-util = "0.7.10" tracing = "0.1.40" tracing-subscriber = "0.3.18" diff --git a/crates/lyrebird/src/fwd/backend.rs b/crates/lyrebird/src/fwd/backend.rs index 047dad6..ad04566 100644 --- a/crates/lyrebird/src/fwd/backend.rs +++ b/crates/lyrebird/src/fwd/backend.rs @@ -7,6 +7,7 @@ use tokio::net::TcpStream; use std::net::SocketAddr; use std::ops::Deref; +use std::pin::Pin; use std::sync::Arc; pub(crate) trait Backend { @@ -15,7 +16,8 @@ pub(crate) trait Backend { &self, conn: In, client_addr: SocketAddr, - ) -> impl Future> + Send + Sync + ) -> Pin> + Send + Sync + '_>> + // ) -> impl Future> + Send + Sync where In: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static; } @@ -54,6 +56,21 @@ impl Backends { #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct BackendArc(Arc); +impl BackendArc { + async fn handle_internal(&self, conn: In, client_addr: SocketAddr) -> Result<()> + where + In: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static, + { + match self.0.as_ref() { + Backends::Echo => server_echo_connection(conn, client_addr).await?, + Backends::Fwd { dst } => server_fwd_connection(conn, dst.parse()?, client_addr).await?, + Backends::Socks { auth: _ } => todo!("not yet implemented"), + } + + Ok(()) + } +} + impl Deref for BackendArc { type Target = Backends; fn deref(&self) -> &Self::Target { @@ -62,17 +79,15 @@ impl Deref for BackendArc { } impl Backend for BackendArc { - async fn handle(&self, conn: In, client_addr: SocketAddr) -> Result<()> + fn handle( + &self, + conn: In, + client_addr: SocketAddr, + ) -> Pin> + Send + Sync + '_>> where In: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static, { - match self.0.as_ref() { - Backends::Echo => server_echo_connection(conn, client_addr).await?, - Backends::Fwd { dst } => server_fwd_connection(conn, dst.parse()?, client_addr).await?, - Backends::Socks { auth: _ } => todo!("not yet implemented"), - } - - Ok(()) + Box::pin(self.handle_internal(conn, client_addr)) } } diff --git a/crates/lyrebird/src/fwd/main.rs b/crates/lyrebird/src/fwd/main.rs index 193cc24..ea78049 100644 --- a/crates/lyrebird/src/fwd/main.rs +++ b/crates/lyrebird/src/fwd/main.rs @@ -336,7 +336,7 @@ async fn client_accept_loop( ) -> Result<()> where // the provided client builder should build the C ClientTransport. - C: ptrs::ClientTransport + 'static, + C: ptrs::ClientTransport + Send + 'static, { let pt_name = C::method_name(); let builder = builder.options(¶ms)?; diff --git a/crates/lyrebird/src/main.rs b/crates/lyrebird/src/main.rs index 8bd4356..62480c4 100644 --- a/crates/lyrebird/src/main.rs +++ b/crates/lyrebird/src/main.rs @@ -316,7 +316,7 @@ async fn client_accept_loop( ) -> Result<()> where // the provided client builder should build the C ClientTransport. - C: ptrs::ClientTransport + 'static, + C: ptrs::ClientTransport + Send + 'static, { let pt_name = C::method_name(); loop { @@ -351,7 +351,7 @@ where // the provided T must be usable as a connection in an async context In: AsyncRead + AsyncWrite + Send + Unpin, // the provided client builder should build the C ClientTransport. - C: ptrs::ClientTransport, + C: ptrs::ClientTransport + Send, { let mut config: fast_socks5::server::Config = fast_socks5::server::Config::default(); diff --git a/crates/o5/Cargo.toml b/crates/o5/Cargo.toml index 6244be9..3875217 100644 --- a/crates/o5/Cargo.toml +++ b/crates/o5/Cargo.toml @@ -13,7 +13,7 @@ rand = { version="0.8.5", features=["getrandom"]} rand_core = "0.6.4" subtle = "2.5.0" -x25519-dalek = { version = "2", features = ["static_secrets", "getrandom", "reusable_secrets", "elligator2"], git = "https://github.com/jmwample/curve25519-dalek.git", branch = "elligator2-ntor"} +x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom", "reusable_secrets", "elligator2"], git = "https://github.com/jmwample/curve25519-dalek.git", branch = "elligator2-ntor"} # ntor_arti zeroize = "1.7.0" diff --git a/crates/o7/Cargo.toml b/crates/o7/Cargo.toml index 371d6f1..83c49ef 100644 --- a/crates/o7/Cargo.toml +++ b/crates/o7/Cargo.toml @@ -24,4 +24,4 @@ ptrs = { path="../ptrs", version="0.1.0" } ## Networking tools tokio = { version = "1.33", features = ["io-util", "rt-multi-thread", "net", "rt", "macros", "sync", "signal", "time", "fs"] } -tokio-util = { version = "0.7.10", features = ["codec", "io", "net"]} +# tokio-util = { version = "0.7.10", features = ["codec", "io", "net"]} diff --git a/crates/obfs4/Cargo.toml b/crates/obfs4/Cargo.toml index d5d0ae4..9b2ef33 100644 --- a/crates/obfs4/Cargo.toml +++ b/crates/obfs4/Cargo.toml @@ -36,7 +36,7 @@ hmac = { version="0.12.1", features=["reset"]} hkdf = "0.12.3" crypto_secretbox = { version="0.1.1", features=["salsa20"]} subtle = "2.5.0" -x25519-dalek = { version = "2", features = ["static_secrets", "getrandom", "reusable_secrets", "elligator2"], git = "https://github.com/jmwample/curve25519-dalek.git", branch = "elligator2-ntor"} +x25519-dalek = { version = "2.0.1", features = ["static_secrets", "getrandom", "reusable_secrets", "elligator2"], git = "https://github.com/jmwample/curve25519-dalek.git", branch = "elligator2-ntor"} ## Utils hex = "0.4.3" @@ -49,7 +49,7 @@ serde = "1.0.197" ## Networking tools pin-project = "1.1.3" futures = "0.3.29" -tokio = { version = "1.33", features = ["io-util", "rt-multi-thread", "net", "rt", "macros", "sync", "signal", "time", "fs"] } +tokio = { version = "1.34", features = ["io-util", "rt-multi-thread", "net", "rt", "macros", "sync", "signal", "time", "fs"] } tokio-util = { version = "0.7.10", features = ["codec", "io"]} bytes = "1.5.0" @@ -62,13 +62,13 @@ cipher = "0.4.4" zeroize = "1.7.0" thiserror = "1.0.56" -# fwd -fast-socks5 = "0.9.1" -url = "2.5.0" -anyhow = "1.0" -tracing-subscriber = "0.3.18" -clap = { version = "4.4.7", features = ["derive"]} -safelog = { version = "0.3.5" } +## transitive dependencies that break things when versions are too low +## i.e. any lower than the exact versions here. +curve25519-dalek = { version="4.1.2", optional=true} +anyhow = { version="1.0.20", optional=true} +async-trait = { version="0.1.9", optional=true} +num-bigint = { version="0.4.2", optional=true} +simple_asn1 = { version="0.6.1", optional=true} ## Maybe useful in future iterations # tor-socksproto = { version = "0.10.0" } @@ -83,3 +83,4 @@ tor-basic-utils = "0.18.0" # o5 pqc test # pqc_kyber = {version="0.7.1", features=["kyber1024", "std"]} # ml-kem = "0.1.0" + diff --git a/crates/obfs4/src/sessions.rs b/crates/obfs4/src/sessions.rs index b760356..1703aa7 100644 --- a/crates/obfs4/src/sessions.rs +++ b/crates/obfs4/src/sessions.rs @@ -36,7 +36,7 @@ pub(crate) struct Established; /// The session broke due to something like a timeout, reset, lost connection, etc. trait Fault {} -pub enum Session { +pub(crate) enum Session { Client(ClientSession), Server(ServerSession), } @@ -140,7 +140,7 @@ impl ClientSession { } } -pub fn new_client_session( +pub(crate) fn new_client_session( station_pubkey: Obfs4NtorPublicKey, iat_mode: IAT, ) -> ClientSession { diff --git a/crates/obfs4/src/testing.rs b/crates/obfs4/src/testing.rs index 185aab1..d2997c9 100644 --- a/crates/obfs4/src/testing.rs +++ b/crates/obfs4/src/testing.rs @@ -254,34 +254,35 @@ async fn transfer_512k_x1() -> Result<()> { let (mut r, mut w) = tokio::io::split(o4c_stream); tokio::spawn(async move { - let msg = [0_u8; 1024 * 512]; - w.write_all(&msg) - .await - .unwrap_or_else(|_| panic!("failed on write")); - w.flush().await.unwrap(); - }); - - let expected_total = 1024 * 512; - let mut buf = vec![0_u8; 1024 * 1024]; - let mut received: usize = 0; - let mut i = 0; - while received < expected_total { - debug!("client read: {i} / {received}"); - tokio::select! { - res = r.read(&mut buf) => { - received += res?; - } - _ = tokio::time::sleep(std::time::Duration::from_millis(2000)) => { - panic!("client failed to read after {i} iterations: timeout"); + let expected_total = 1024 * 512; + let mut buf = vec![0_u8; 1024 * 513]; + let mut received: usize = 0; + let mut i = 0; + while received < expected_total { + debug!("client read: {i} / {received}"); + tokio::select! { + res = r.read(&mut buf) => { + received += res.unwrap(); + } + _ = tokio::time::sleep(std::time::Duration::from_millis(2000)) => { + panic!("client failed to read after {i} iterations: timeout"); + } } + i += 1; } - i += 1; - } - assert_eq!( - received, expected_total, - "incorrect amount received {received} != {expected_total}" - ); + assert_eq!( + received, expected_total, + "incorrect amount received {received} != {expected_total}" + ); + }); + + let msg = [0_u8; 1024 * 512]; + w.write_all(&msg) + .await + .unwrap_or_else(|_| panic!("failed on write")); + w.flush().await?; + Ok(()) } diff --git a/crates/ptrs/Cargo.toml b/crates/ptrs/Cargo.toml index 970d2b3..abca46e 100644 --- a/crates/ptrs/Cargo.toml +++ b/crates/ptrs/Cargo.toml @@ -3,7 +3,7 @@ name = "ptrs" version = "0.1.0" edition = "2021" authors = ["Jack Wampler "] -rust-version = "1.70" +rust-version = "1.63" license = "MIT OR Apache-2.0" description = "Interdaces and utilities supporting pluggable transport implementations" keywords = ["tor", "censorship", "pluggable", "transports"] @@ -24,7 +24,7 @@ futures = "0.3.30" itertools = "0.12.1" subtle = "2.5.0" thiserror = "1" -tokio = { version = "1.33", features = ["full"] } +tokio = { version = "1.34", features = ["full"] } tracing = "0.1.40" url = "2.5.0" diff --git a/crates/ptrs/src/helpers.rs b/crates/ptrs/src/helpers.rs index f8f9216..18b75b3 100644 --- a/crates/ptrs/src/helpers.rs +++ b/crates/ptrs/src/helpers.rs @@ -81,7 +81,11 @@ pub fn make_state_dir() -> Result { /// Feature #15435 adds a new env var for determining if Tor keeps stdin /// open for use in termination detection. pub fn pt_should_exit_on_stdin_close() -> bool { - env::var(constants::EXIT_ON_STDIN_CLOSE).is_ok_and(|v| v == "1") + if let Ok(v) = env::var(constants::EXIT_ON_STDIN_CLOSE) { + v == "1" + } else { + false + } } // ================================================================ // @@ -175,11 +179,15 @@ pub(crate) fn validate_proxy_url(spec: &Url) -> Result<(), Error> { return Err(to_io_other("proxy URI has a path defined ")); } } - if spec.query().is_some_and(|s| !s.is_empty()) { - return Err(to_io_other("proxy URI has a query defined")); + if spec.query().is_some() { + if !spec.query().unwrap().is_empty() { + return Err(to_io_other("proxy URI has a query defined")); + } } - if spec.fragment().is_some_and(|s| !s.is_empty()) { - return Err(to_io_other("proxy URI has a fragment defined")); + if spec.fragment().is_some() { + if !spec.fragment().unwrap().is_empty() { + return Err(to_io_other("proxy URI has a fragment defined")); + } } if spec.port().is_none() { return Err(to_io_other("proxy URI lacks a port")); @@ -195,8 +203,12 @@ pub(crate) fn validate_proxy_url(spec: &Url) -> Result<(), Error> { if username.is_empty() || username.len() > 255 { return Err(to_io_other("proxy URI specified a invalid SOCKS5 username")); } - if passwd.is_none() || passwd.is_some_and(|p| p.is_empty() || p.len() > 255) { + if passwd.is_none() { return Err(to_io_other("proxy URI specified a invalid SOCKS5 password")); + } else if let Some(p) = passwd { + if p.is_empty() || p.len() > 255 { + return Err(to_io_other("proxy URI specified a invalid SOCKS5 password")); + } } } } @@ -450,11 +462,15 @@ mod test { env::set_var(constants::CLIENT_TRANSPORTS, "trebuchet"); env::remove_var(constants::SERVER_TRANSPORTS); - assert!(is_client().is_ok_and(|is_client| is_client)); + let c = is_client(); + assert!(c.is_ok()); + assert!(c.unwrap()); env::remove_var(constants::CLIENT_TRANSPORTS); env::set_var(constants::SERVER_TRANSPORTS, "trebuchet1"); - assert!(is_client().is_ok_and(|is_client| !is_client)); + let c = is_client(); + assert!(c.is_ok()); + assert!(!c.unwrap()); env::set_var(constants::CLIENT_TRANSPORTS, "trebuchet2"); env::set_var(constants::SERVER_TRANSPORTS, "trebuchet2"); @@ -617,7 +633,9 @@ mod test { #[test] fn validate_url() -> Result<(), Error> { env::remove_var(constants::PROXY); - assert!(get_proxy_url().is_ok_and(|url| url.is_none())); + let url = get_proxy_url(); + assert!(url.is_ok()); + assert!(url.unwrap().is_none()); let bad_url = vec![ "asdals;kdmma", diff --git a/crates/ptrs/src/passthrough.rs b/crates/ptrs/src/passthrough.rs index 6009d8a..003b0e1 100644 --- a/crates/ptrs/src/passthrough.rs +++ b/crates/ptrs/src/passthrough.rs @@ -590,13 +590,14 @@ mod design_tests { <>::ServerPT as ServerTransport>::OutErr, >, >, - Box, + Box, > where T: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static, B: ServerBuilder, B::ServerPT: ServerTransport, B::Error: std::error::Error + 'static, + >::Error: std::error::Error + Send + Sync, { Ok(pt_builder .statefile_location("./")?