aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar sunmy2019 <59365878+sunmy2019@users.noreply.github.com> 2024-02-18 17:17:17 +0800
committerGravatar GitHub <noreply@github.com> 2024-02-18 17:17:17 +0800
commit4ac53a5a39e74d5eb12bee22d0fd4783acaae670 (patch)
treeca442e698e6870b7f6c5053b54fff82e2a95626e
parent7251759bdaf4b7d170575bdd6d2062bbd9f338bb (diff)
downloadrathole-4ac53a5a39e74d5eb12bee22d0fd4783acaae670.tar.gz
rathole-4ac53a5a39e74d5eb12bee22d0fd4783acaae670.tar.zst
rathole-4ac53a5a39e74d5eb12bee22d0fd4783acaae670.zip
feat: optional rustls support (#330)
* initial implementation of rustls support * Refactor create_self_signed_cert.sh script * resolve lint errors * Fix handling of Option in tls.rs * Update cargo-hack check command and feature dependencies * fix missing point * Add conditional check to skip test if client or server is not enabled * clean up things * fix for windows CI * try fixing Windows CI * Update src/main.rs * Update src/transport/websocket.rs * add missing messages * split the tls mod Co-authored-by: Ning Sun <n@sunng.info>
-rw-r--r--.github/workflows/rust.yml8
-rw-r--r--Cargo.lock164
-rw-r--r--Cargo.toml71
-rw-r--r--examples/tls/create_self_signed_cert.sh5
-rw-r--r--examples/tls/identity.pfxbin3587 -> 3453 bytes
-rw-r--r--examples/tls/rootCA.crt32
-rw-r--r--src/client.rs19
-rw-r--r--src/helper.rs8
-rw-r--r--src/lib.rs9
-rw-r--r--src/server.rs16
-rw-r--r--src/transport/mod.rs23
-rw-r--r--src/transport/native_tls.rs (renamed from src/transport/tls.rs)13
-rw-r--r--src/transport/rustls.rs156
-rw-r--r--src/transport/websocket.rs14
-rw-r--r--tests/integration_test.rs23
15 files changed, 482 insertions, 79 deletions
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 45c693a..916c0ed 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -32,7 +32,9 @@ jobs:
- name: Setup cargo-hack
run: cargo install cargo-hack
- name: Check all features
- run: cargo hack check --feature-powerset --no-dev-deps
+ run: >
+ cargo hack check --feature-powerset --no-dev-deps
+ --mutually-exclusive-features default,native-tls,websocket-native-tls,rustls,websocket-rustls
build:
name: Build for ${{ matrix.target }}
@@ -62,8 +64,10 @@ jobs:
- uses: Swatinem/rust-cache@v1
- name: Build
run: cargo build
- - name: Run tests
+ - name: Run tests with native-tls
run: cargo test --verbose
+ - name: Run tests with rustls
+ run: cargo test --verbose --no-default-features --features server,client,rustls,noise,websocket-rustls,hot-reload
- uses: actions/upload-artifact@v2
with:
name: rathole-${{ matrix.target }}
diff --git a/Cargo.lock b/Cargo.lock
index ce79bad..77a6517 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -244,6 +244,15 @@ dependencies = [
]
[[package]]
+name = "block-padding"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -259,6 +268,15 @@ dependencies = [
]
[[package]]
+name = "cbc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -498,6 +516,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]]
+name = "des"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -851,6 +878,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -982,6 +1018,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
+ "block-padding",
"generic-array",
]
@@ -1313,6 +1350,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
+name = "p12"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224"
+dependencies = [
+ "cbc",
+ "cipher",
+ "des",
+ "getrandom",
+ "hmac",
+ "lazy_static",
+ "rc2",
+ "sha1",
+ "yasna",
+]
+
+[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1540,13 +1594,17 @@ dependencies = [
"lazy_static",
"notify",
"openssl",
+ "p12",
"rand",
+ "rustls-native-certs",
+ "rustls-pemfile",
"serde",
"sha2",
"snowstorm",
"socket2 0.4.9",
"tokio",
"tokio-native-tls",
+ "tokio-rustls",
"tokio-tungstenite",
"tokio-util",
"toml",
@@ -1557,6 +1615,15 @@ dependencies = [
]
[[package]]
+name = "rc2"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1610,6 +1677,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
+name = "ring"
+version = "0.17.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
+dependencies = [
+ "cc",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1638,6 +1719,60 @@ dependencies = [
]
[[package]]
+name = "rustls"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4"
+dependencies = [
+ "base64 0.21.4",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1839,6 +1974,12 @@ dependencies = [
]
[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2031,6 +2172,17 @@ dependencies = [
]
[[package]]
+name = "tokio-rustls"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
+dependencies = [
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+]
+
+[[package]]
name = "tokio-stream"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2262,6 +2414,12 @@ dependencies = [
]
[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
name = "url"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2502,6 +2660,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
+name = "yasna"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
+
+[[package]]
name = "zeroize"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index bcd2295..44fcf97 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,18 +11,48 @@ build = "build.rs"
include = ["src/**/*", "LICENSE", "README.md", "build.rs"]
[features]
-default = ["server", "client", "tls", "noise", "websocket", "hot-reload"]
+default = [
+ "server",
+ "client",
+ "native-tls",
+ "noise",
+ "websocket-native-tls",
+ "hot-reload",
+]
# Run as a server
server = []
# Run as a client
client = []
+
# TLS support
-tls = ["tokio-native-tls"]
+native-tls = ["tokio-native-tls"]
+rustls = [
+ "tokio-rustls",
+ "rustls-pemfile",
+ "rustls-native-certs",
+ "p12",
+]
+
# Noise support
noise = ["snowstorm", "base64"]
+
# Websocket support
-websocket = ["tokio-tungstenite", "tokio-util", "futures-core", "futures-sink", "tls"]
+websocket-native-tls = [
+ "tokio-tungstenite",
+ "tokio-util",
+ "futures-core",
+ "futures-sink",
+ "native-tls",
+]
+websocket-rustls = [
+ "tokio-tungstenite",
+ "tokio-util",
+ "futures-core",
+ "futures-sink",
+ "rustls",
+]
+
# Configuration hot-reload support
hot-reload = ["notify"]
@@ -67,27 +97,42 @@ hex = "0.4"
rand = "0.8"
backoff = { version = "0.4", features = ["tokio"] }
tracing = "0.1"
-tracing-subscriber = { version="0.3", features=["env-filter"] }
+tracing-subscriber = { version = "0.3", features = ["env-filter"] }
socket2 = { version = "0.4", features = ["all"] }
fdlimit = "0.2"
-tokio-native-tls = { version = "0.3", optional = true }
async-trait = "0.1"
-snowstorm = { version = "0.4", optional = true, features = ["stream"], default-features = false }
+snowstorm = { version = "0.4", optional = true, features = [
+ "stream",
+], default-features = false }
base64 = { version = "0.13", optional = true }
notify = { version = "5.0.0-pre.13", optional = true }
-console-subscriber = { version = "0.1", optional = true, features = ["parking_lot"] }
+console-subscriber = { version = "0.1", optional = true, features = [
+ "parking_lot",
+] }
atty = "0.2"
-async-http-proxy = { version = "1.2", features = ["runtime-tokio", "basic-auth"] }
+async-http-proxy = { version = "1.2", features = [
+ "runtime-tokio",
+ "basic-auth",
+] }
async-socks5 = "0.5"
url = { version = "2.2", features = ["serde"] }
-tokio-tungstenite = { version="0.20.1", optional = true}
-tokio-util = { version="0.7.9", optional = true, features = ["io"] }
-futures-core = { version="0.3.28", optional = true }
-futures-sink = { version="0.3.28", optional = true }
+tokio-tungstenite = { version = "0.20.1", optional = true }
+tokio-util = { version = "0.7.9", optional = true, features = ["io"] }
+futures-core = { version = "0.3.28", optional = true }
+futures-sink = { version = "0.3.28", optional = true }
+tokio-native-tls = { version = "0.3", optional = true }
+tokio-rustls = { version = "0.25", optional = true }
+rustls-native-certs = { version = "0.7", optional = true }
+rustls-pemfile = { version = "2.0", optional = true }
+p12 = { version = "0.6.3", optional = true }
[target.'cfg(target_env = "musl")'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
[build-dependencies]
-vergen = { version = "7.4.2", default-features = false, features = ["build", "git", "cargo"] }
+vergen = { version = "7.4.2", default-features = false, features = [
+ "build",
+ "git",
+ "cargo",
+] }
anyhow = "1.0"
diff --git a/examples/tls/create_self_signed_cert.sh b/examples/tls/create_self_signed_cert.sh
index e110a1f..6ab9db3 100644
--- a/examples/tls/create_self_signed_cert.sh
+++ b/examples/tls/create_self_signed_cert.sh
@@ -56,7 +56,8 @@ openssl x509 -req \
-sha256 -extfile cert.conf
# create pkcs12
-openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile rootCA.crt -passout pass:1234
+openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile rootCA.crt \
+ -passout pass:1234 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES
# clean up
-rm server.csr csr.conf cert.conf \ No newline at end of file
+rm server.csr csr.conf cert.conf
diff --git a/examples/tls/identity.pfx b/examples/tls/identity.pfx
index dfc5160..2041157 100644
--- a/examples/tls/identity.pfx
+++ b/examples/tls/identity.pfx
Binary files differ
diff --git a/examples/tls/rootCA.crt b/examples/tls/rootCA.crt
index f3ed90f..b48e63d 100644
--- a/examples/tls/rootCA.crt
+++ b/examples/tls/rootCA.crt
@@ -1,20 +1,20 @@
-----BEGIN CERTIFICATE-----
-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
+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
-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
+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
-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
+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
-----END CERTIFICATE-----
diff --git a/src/client.rs b/src/client.rs
index 95a5a74..2564869 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -8,8 +8,9 @@ use crate::protocol::{
};
use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
use anyhow::{anyhow, bail, Context, Result};
+use backoff::backoff::Backoff;
+use backoff::future::retry_notify;
use backoff::ExponentialBackoff;
-use backoff::{backoff::Backoff, future::retry_notify};
use bytes::{Bytes, BytesMut};
use std::collections::HashMap;
use std::net::SocketAddr;
@@ -22,9 +23,9 @@ use tracing::{debug, error, info, instrument, trace, warn, Instrument, Span};
#[cfg(feature = "noise")]
use crate::transport::NoiseTransport;
-#[cfg(feature = "tls")]
+#[cfg(any(feature = "native-tls", feature = "rustls"))]
use crate::transport::TlsTransport;
-#[cfg(feature = "websocket")]
+#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
use crate::transport::WebsocketTransport;
use crate::constants::{run_control_chan_backoff, UDP_BUFFER_SIZE, UDP_SENDQ_SIZE, UDP_TIMEOUT};
@@ -47,13 +48,13 @@ pub async fn run_client(
client.run(shutdown_rx, update_rx).await
}
TransportType::Tls => {
- #[cfg(feature = "tls")]
+ #[cfg(any(feature = "native-tls", feature = "rustls"))]
{
let mut client = Client::<TlsTransport>::from(config).await?;
client.run(shutdown_rx, update_rx).await
}
- #[cfg(not(feature = "tls"))]
- crate::helper::feature_not_compile("tls")
+ #[cfg(not(any(feature = "native-tls", feature = "rustls")))]
+ crate::helper::feature_neither_compile("native-tls", "rustls")
}
TransportType::Noise => {
#[cfg(feature = "noise")]
@@ -65,13 +66,13 @@ pub async fn run_client(
crate::helper::feature_not_compile("noise")
}
TransportType::Websocket => {
- #[cfg(feature = "websocket")]
+ #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
{
let mut client = Client::<WebsocketTransport>::from(config).await?;
client.run(shutdown_rx, update_rx).await
}
- #[cfg(not(feature = "websocket"))]
- crate::helper::feature_not_compile("websocket")
+ #[cfg(not(any(feature = "websocket-native-tls", feature = "websocket-rustls")))]
+ crate::helper::feature_neither_compile("websocket-native-tls", "websocket-rustls")
}
}
}
diff --git a/src/helper.rs b/src/helper.rs
index 7e1e5d3..a292969 100644
--- a/src/helper.rs
+++ b/src/helper.rs
@@ -43,6 +43,14 @@ pub fn feature_not_compile(feature: &str) -> ! {
)
}
+#[allow(dead_code)]
+pub fn feature_neither_compile(feature1: &str, feature2: &str) -> ! {
+ panic!(
+ "Neither of the feature '{}' or '{}' is compiled in this binary. Please re-compile rathole",
+ feature1, feature2
+ )
+}
+
pub async fn to_socket_addr<A: ToSocketAddrs>(addr: A) -> Result<SocketAddr> {
lookup_host(addr)
.await?
diff --git a/src/lib.rs b/src/lib.rs
index 7fb2fa6..65beb7f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -83,7 +83,7 @@ pub async fn run(args: Cli, shutdown_rx: broadcast::Receiver<bool>) -> Result<()
if let Some((i, _)) = last_instance {
info!("General configuration change detected. Restarting...");
shutdown_tx.send(true)?;
- i.await?;
+ i.await??;
}
debug!("{:?}", config);
@@ -119,8 +119,8 @@ async fn run_instance(
args: Cli,
shutdown_rx: broadcast::Receiver<bool>,
service_update: mpsc::Receiver<ConfigChange>,
-) {
- let ret: Result<()> = match determine_run_mode(&config, &args) {
+) -> Result<()> {
+ match determine_run_mode(&config, &args) {
RunMode::Undetermine => panic!("Cannot determine running as a server or a client"),
RunMode::Client => {
#[cfg(not(feature = "client"))]
@@ -134,8 +134,7 @@ async fn run_instance(
#[cfg(feature = "server")]
run_server(config, shutdown_rx, service_update).await
}
- };
- ret.unwrap();
+ }
}
#[derive(PartialEq, Eq, Debug)]
diff --git a/src/server.rs b/src/server.rs
index 83ae976..a4c4948 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -25,9 +25,9 @@ use tracing::{debug, error, info, info_span, instrument, warn, Instrument, Span}
#[cfg(feature = "noise")]
use crate::transport::NoiseTransport;
-#[cfg(feature = "tls")]
+#[cfg(any(feature = "native-tls", feature = "rustls"))]
use crate::transport::TlsTransport;
-#[cfg(feature = "websocket")]
+#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
use crate::transport::WebsocketTransport;
type ServiceDigest = protocol::Digest; // SHA256 of a service name
@@ -57,13 +57,13 @@ pub async fn run_server(
server.run(shutdown_rx, update_rx).await?;
}
TransportType::Tls => {
- #[cfg(feature = "tls")]
+ #[cfg(any(feature = "native-tls", feature = "rustls"))]
{
let mut server = Server::<TlsTransport>::from(config).await?;
server.run(shutdown_rx, update_rx).await?;
}
- #[cfg(not(feature = "tls"))]
- crate::helper::feature_not_compile("tls")
+ #[cfg(not(any(feature = "native-tls", feature = "rustls")))]
+ crate::helper::feature_neither_compile("native-tls", "rustls")
}
TransportType::Noise => {
#[cfg(feature = "noise")]
@@ -75,13 +75,13 @@ pub async fn run_server(
crate::helper::feature_not_compile("noise")
}
TransportType::Websocket => {
- #[cfg(feature = "websocket")]
+ #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
{
let mut server = Server::<WebsocketTransport>::from(config).await?;
server.run(shutdown_rx, update_rx).await?;
}
- #[cfg(not(feature = "websocket"))]
- crate::helper::feature_not_compile("websocket")
+ #[cfg(not(any(feature = "websocket-native-tls", feature = "websocket-rustls")))]
+ crate::helper::feature_neither_compile("websocket-native-tls", "websocket-rustls")
}
}
diff --git a/src/transport/mod.rs b/src/transport/mod.rs
index 38682a6..26d357f 100644
--- a/src/transport/mod.rs
+++ b/src/transport/mod.rs
@@ -69,19 +69,30 @@ pub trait Transport: Debug + Send + Sync {
mod tcp;
pub use tcp::TcpTransport;
-#[cfg(feature = "tls")]
-mod tls;
-#[cfg(feature = "tls")]
-pub use tls::TlsTransport;
+
+#[cfg(all(feature = "native-tls", feature = "rustls"))]
+compile_error!("Only one of `native-tls` and `rustls` can be enabled");
+
+#[cfg(feature = "native-tls")]
+mod native_tls;
+#[cfg(feature = "native-tls")]
+use native_tls as tls;
+#[cfg(feature = "rustls")]
+mod rustls;
+#[cfg(feature = "rustls")]
+use rustls as tls;
+
+#[cfg(any(feature = "native-tls", feature = "rustls"))]
+pub(crate) use tls::TlsTransport;
#[cfg(feature = "noise")]
mod noise;
#[cfg(feature = "noise")]
pub use noise::NoiseTransport;
-#[cfg(feature = "websocket")]
+#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
mod websocket;
-#[cfg(feature = "websocket")]
+#[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
pub use websocket::WebsocketTransport;
#[derive(Debug, Clone, Copy)]
diff --git a/src/transport/tls.rs b/src/transport/native_tls.rs
index 918af04..40afd50 100644
--- a/src/transport/tls.rs
+++ b/src/transport/native_tls.rs
@@ -1,14 +1,14 @@
-use std::net::SocketAddr;
-
-use super::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
use crate::config::{TlsConfig, TransportConfig};
use crate::helper::host_port_pair;
+use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use std::fs;
+use std::net::SocketAddr;
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
use tokio_native_tls::native_tls::{self, Certificate, Identity};
-use tokio_native_tls::{TlsAcceptor, TlsConnector, TlsStream};
+pub(crate) use tokio_native_tls::TlsStream;
+use tokio_native_tls::{TlsAcceptor, TlsConnector};
#[derive(Debug)]
pub struct TlsTransport {
@@ -109,3 +109,8 @@ impl Transport for TlsTransport {
.await?)
}
}
+
+#[cfg(feature = "websocket-native-tls")]
+pub(crate) fn get_tcpstream(s: &TlsStream<TcpStream>) -> &TcpStream {
+ s.get_ref().get_ref().get_ref()
+}
diff --git a/src/transport/rustls.rs b/src/transport/rustls.rs
new file mode 100644
index 0000000..3ca4704
--- /dev/null
+++ b/src/transport/rustls.rs
@@ -0,0 +1,156 @@
+use crate::config::{TlsConfig, TransportConfig};
+use crate::helper::host_port_pair;
+use crate::transport::{AddrMaybeCached, SocketOpts, TcpTransport, Transport};
+use std::fmt::Debug;
+use std::fs;
+use std::net::SocketAddr;
+use std::sync::Arc;
+use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
+use tokio_rustls::rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer, ServerName};
+
+use anyhow::{anyhow, Context, Result};
+use async_trait::async_trait;
+use p12::PFX;
+use tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig};
+pub(crate) use tokio_rustls::TlsStream;
+use tokio_rustls::{TlsAcceptor, TlsConnector};
+
+pub struct TlsTransport {
+ tcp: TcpTransport,
+ config: TlsConfig,
+ connector: Option<TlsConnector>,
+ tls_acceptor: Option<TlsAcceptor>,
+}
+
+// workaround for TlsConnector and TlsAcceptor not implementing Debug
+impl Debug for TlsTransport {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("TlsTransport")
+ .field("tcp", &self.tcp)
+ .field("config", &self.config)
+ .finish()
+ }
+}
+
+fn load_server_config(config: &TlsConfig) -> Result<Option<ServerConfig>> {
+ if let Some(pkcs12_path) = config.pkcs12.as_ref() {
+ let buf = fs::read(pkcs12_path)?;
+ let pfx = PFX::parse(buf.as_slice())?;
+ let pass = config.pkcs12_password.as_ref().unwrap();
+
+ let certs = pfx.cert_bags(pass)?;
+ let keys = pfx.key_bags(pass)?;
+
+ let chain: Vec<CertificateDer> = certs.into_iter().map(CertificateDer::from).collect();
+ let key = PrivatePkcs8KeyDer::from(keys.into_iter().next().unwrap());
+
+ Ok(Some(
+ ServerConfig::builder()
+ .with_no_client_auth()
+ .with_single_cert(chain, key.into())?,
+ ))
+ } else {
+ Ok(None)
+ }
+}
+
+fn load_client_config(config: &TlsConfig) -> Result<Option<ClientConfig>> {
+ let cert = if let Some(path) = config.trusted_root.as_ref() {
+ rustls_pemfile::certs(&mut std::io::BufReader::new(fs::File::open(path).unwrap()))
+ .map(|cert| cert.unwrap())
+ .next()
+ .with_context(|| "Failed to read certificate")?
+ } else {
+ // read from native
+ match rustls_native_certs::load_native_certs() {
+ Ok(certs) => certs.into_iter().next().unwrap(),
+ Err(e) => {
+ eprintln!("Failed to load native certs: {}", e);
+ return Ok(None);
+ }
+ }
+ };
+
+ let mut root_certs = RootCertStore::empty();
+ root_certs.add(cert).unwrap();
+
+ Ok(Some(
+ ClientConfig::builder()
+ .with_root_certificates(root_certs)
+ .with_no_client_auth(),
+ ))
+}
+
+#[async_trait]
+impl Transport for TlsTransport {
+ type Acceptor = TcpListener;
+ type RawStream = TcpStream;
+ type Stream = TlsStream<TcpStream>;
+
+ fn new(config: &TransportConfig) -> Result<Self> {
+ let tcp = TcpTransport::new(config)?;
+ let config = config
+ .tls
+ .as_ref()
+ .ok_or_else(|| anyhow!("Missing tls config"))?;
+
+ let connector = load_client_config(config)
+ .unwrap()
+ .map(|c| Arc::new(c).into());
+ let tls_acceptor = load_server_config(config)
+ .unwrap()
+ .map(|c| Arc::new(c).into());
+
+ Ok(TlsTransport {
+ tcp,
+ config: config.clone(),
+ connector,
+ tls_acceptor,
+ })
+ }
+
+ fn hint(conn: &Self::Stream, opt: SocketOpts) {
+ opt.apply(conn.get_ref().0);
+ }
+
+ async fn bind<A: ToSocketAddrs + Send + Sync>(&self, addr: A) -> Result<Self::Acceptor> {
+ let l = TcpListener::bind(addr)
+ .await
+ .with_context(|| "Failed to create tcp listener")?;
+ Ok(l)
+ }
+
+ async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> {
+ self.tcp
+ .accept(a)
+ .await
+ .with_context(|| "Failed to accept TCP connection")
+ }
+
+ async fn handshake(&self, conn: Self::RawStream) -> Result<Self::Stream> {
+ let conn = self.tls_acceptor.as_ref().unwrap().accept(conn).await?;
+ Ok(tokio_rustls::TlsStream::Server(conn))
+ }
+
+ async fn connect(&self, addr: &AddrMaybeCached) -> Result<Self::Stream> {
+ let conn = self.tcp.connect(addr).await?;
+
+ let connector = self.connector.as_ref().unwrap();
+
+ let host_name = self
+ .config
+ .hostname
+ .as_deref()
+ .unwrap_or(host_port_pair(&addr.addr)?.0);
+
+ Ok(tokio_rustls::TlsStream::Client(
+ connector
+ .connect(ServerName::try_from(host_name)?.to_owned(), conn)
+ .await?,
+ ))
+ }
+}
+
+pub(crate) fn get_tcpstream(s: &TlsStream<TcpStream>) -> &TcpStream {
+ &s.get_ref().0
+}
diff --git a/src/transport/websocket.rs b/src/transport/websocket.rs
index ec6177d..228eff7 100644
--- a/src/transport/websocket.rs
+++ b/src/transport/websocket.rs
@@ -13,10 +13,14 @@ use futures_core::stream::Stream;
use futures_sink::Sink;
use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
-use tokio_native_tls::TlsStream;
-use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
-use tokio_tungstenite::{accept_async_with_config, client_async_with_config};
-use tokio_tungstenite::{tungstenite::protocol::Message, WebSocketStream};
+
+#[cfg(any(feature = "native-tls", feature = "rustls"))]
+use super::tls::get_tcpstream;
+#[cfg(any(feature = "native-tls", feature = "rustls"))]
+use super::tls::TlsStream;
+
+use tokio_tungstenite::tungstenite::protocol::{Message, WebSocketConfig};
+use tokio_tungstenite::{accept_async_with_config, client_async_with_config, WebSocketStream};
use tokio_util::io::StreamReader;
use url::Url;
@@ -30,7 +34,7 @@ impl TransportStream {
fn get_tcpstream(&self) -> &TcpStream {
match self {
TransportStream::Insecure(s) => s,
- TransportStream::Secure(s) => s.get_ref().get_ref().get_ref(),
+ TransportStream::Secure(s) => get_tcpstream(s),
}
}
}
diff --git a/tests/integration_test.rs b/tests/integration_test.rs
index 3b8dec9..7b5d408 100644
--- a/tests/integration_test.rs
+++ b/tests/integration_test.rs
@@ -1,4 +1,4 @@
-use anyhow::Result;
+use anyhow::{Ok, Result};
use common::{run_rathole_client, PING, PONG};
use rand::Rng;
use std::time::Duration;
@@ -57,17 +57,17 @@ async fn tcp() -> Result<()> {
test("tests/for_tcp/tcp_transport.toml", Type::Tcp).await?;
// FIXME: Self-signed certificate on Mac requires mannual interference. Disable CI for now
#[cfg(not(target_os = "macos"))]
- #[cfg(feature="tls")]
+ #[cfg(any(feature = "native-tls", feature = "rustls"))]
test("tests/for_tcp/tls_transport.toml", Type::Tcp).await?;
- #[cfg(feature="noise")]
+ #[cfg(feature = "noise")]
test("tests/for_tcp/noise_transport.toml", Type::Tcp).await?;
- #[cfg(feature="websocket")]
+ #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
test("tests/for_tcp/websocket_transport.toml", Type::Tcp).await?;
#[cfg(not(target_os = "macos"))]
- #[cfg(feature="websocket")]
+ #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
test("tests/for_tcp/websocket_tls_transport.toml", Type::Tcp).await?;
Ok(())
@@ -94,17 +94,17 @@ async fn udp() -> Result<()> {
test("tests/for_udp/tcp_transport.toml", Type::Udp).await?;
// See above
#[cfg(not(target_os = "macos"))]
- #[cfg(feature="tls")]
+ #[cfg(any(feature = "native-tls", feature = "rustls"))]
test("tests/for_udp/tls_transport.toml", Type::Udp).await?;
- #[cfg(feature="noise")]
+ #[cfg(feature = "noise")]
test("tests/for_udp/noise_transport.toml", Type::Udp).await?;
- #[cfg(feature="websocket")]
+ #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
test("tests/for_udp/websocket_transport.toml", Type::Udp).await?;
#[cfg(not(target_os = "macos"))]
- #[cfg(feature="websocket")]
+ #[cfg(any(feature = "websocket-native-tls", feature = "websocket-rustls"))]
test("tests/for_udp/websocket_tls_transport.toml", Type::Udp).await?;
Ok(())
@@ -112,6 +112,11 @@ async fn udp() -> Result<()> {
#[instrument]
async fn test(config_path: &'static str, t: Type) -> Result<()> {
+ if cfg!(not(all(feature = "client", feature = "server"))) {
+ // Skip the test if the client or the server is not enabled
+ return Ok(());
+ }
+
let (client_shutdown_tx, client_shutdown_rx) = broadcast::channel(1);
let (server_shutdown_tx, server_shutdown_rx) = broadcast::channel(1);