diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 41f537f9..abd35b26 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -28,3 +28,4 @@ If you encountered a panic, please re-run with `RUST_BACKTRACE=1` to provide the - OS: - `rathole --version` output: - CPU architecture: +- rustc version: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 353cf2e5..7cf53455 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,47 +20,66 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu exe: rathole + cross: false - os: ubuntu-latest target: x86_64-unknown-linux-musl exe: rathole + cross: false - os: ubuntu-latest target: aarch64-unknown-linux-musl exe: rathole + cross: true - os: ubuntu-latest target: arm-unknown-linux-musleabi exe: rathole + cross: true - os: ubuntu-latest target: arm-unknown-linux-musleabihf exe: rathole + cross: true - os: ubuntu-latest target: armv7-unknown-linux-musleabihf exe: rathole + cross: true - os: ubuntu-latest target: mips-unknown-linux-gnu exe: rathole + cross: true - os: ubuntu-latest target: mips-unknown-linux-musl exe: rathole + cross: true - os: ubuntu-latest target: mipsel-unknown-linux-gnu exe: rathole + cross: true - os: ubuntu-latest target: mipsel-unknown-linux-musl exe: rathole + cross: true - os: ubuntu-latest target: mips64-unknown-linux-gnuabi64 exe: rathole + cross: true - os: ubuntu-latest target: mips64el-unknown-linux-gnuabi64 exe: rathole + cross: true - os: macos-latest target: x86_64-apple-darwin exe: rathole + cross: false + + - os: macos-latest + target: aarch64-apple-darwin + exe: rathole + cross: false - os: windows-latest target: x86_64-pc-windows-msvc exe: rathole.exe + cross: false steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -76,6 +95,40 @@ jobs: run: cross test --release --target ${{ matrix.target }} --verbose --features ${RATHOLE_FEATURES:-default} - name: Build release run: cross build --release --target ${{ matrix.target }} --features ${RATHOLE_FEATURES:-default} + # Since rust 1.72, some platforms are tier 3 + toolchain: 1.71 + default: true + + - name: Install OpenSSL + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get install pkg-config libssl-dev + - name: Install OpenSSL + if: matrix.os == 'macos-latest' + run: brew install openssl@3 + + # Native build + - name: Install target + if: matrix.cross == false + run: rustup target add ${{ matrix.target }} + - name: Run tests + if: matrix.cross == false && matrix.target != 'aarch64-apple-darwin' + run: cargo test --release --target ${{ matrix.target }} --verbose + - name: Build release + if: matrix.cross == false + run: cargo build --release --target ${{ matrix.target }} + + # Cross build + - name: Install cross + if: matrix.cross + run: cargo install --version 0.2.5 cross + - name: Run tests + if: matrix.cross + run: cross test --release --target ${{ matrix.target }} --verbose --features embedded --no-default-features + - name: Build release + if: matrix.cross + run: cross build --release --target ${{ matrix.target }} --features embedded --no-default-features + + - name: Run UPX # Upx may not support some platforms. Ignore the errors continue-on-error: true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 45c693ab..916c0edf 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 fe7691f6..77a6517c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,30 +19,30 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ + "crypto-common", "generic-array", ] [[package]] name = "aes" -version = "0.7.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", - "opaque-debug", ] [[package]] name = "aes-gcm" -version = "0.9.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", @@ -243,6 +243,15 @@ dependencies = [ "generic-array", ] +[[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" @@ -258,6 +267,15 @@ dependencies = [ "serde", ] +[[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" @@ -276,21 +294,20 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.8.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", "cpufeatures", - "zeroize", ] [[package]] name = "chacha20poly1305" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20", @@ -301,11 +318,13 @@ dependencies = [ [[package]] name = "cipher" -version = "0.3.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "generic-array", + "crypto-common", + "inout", + "zeroize", ] [[package]] @@ -318,7 +337,7 @@ dependencies = [ "bitflags 1.3.2", "clap_derive", "clap_lex", - "indexmap", + "indexmap 1.9.3", "once_cell", "strsim", "termcolor", @@ -444,23 +463,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] [[package]] name = "ctr" -version = "0.7.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher", ] [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -495,6 +515,15 @@ version = "0.3.8" 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" @@ -532,6 +561,12 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.3" @@ -744,9 +779,9 @@ dependencies = [ [[package]] name = "ghash" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug", "polyval", @@ -773,9 +808,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -783,7 +818,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -796,6 +831,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "hdrhistogram" version = "7.5.2" @@ -836,6 +877,15 @@ version = "0.4.3" 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" @@ -929,7 +979,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", ] [[package]] @@ -952,6 +1012,16 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1245,6 +1315,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-src" +version = "300.2.3+3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.93" @@ -1253,6 +1332,7 @@ checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -1269,6 +1349,23 @@ version = "0.1.1" 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" @@ -1344,9 +1441,9 @@ checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" [[package]] name = "poly1305" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug", @@ -1355,9 +1452,9 @@ dependencies = [ [[package]] name = "polyval" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" dependencies = [ "cfg-if", "cpufeatures", @@ -1477,7 +1574,7 @@ dependencies = [ [[package]] name = "rathole" -version = "0.4.9" +version = "0.5.0" dependencies = [ "anyhow", "async-http-proxy", @@ -1496,13 +1593,18 @@ dependencies = [ "hex", "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", @@ -1512,6 +1614,15 @@ dependencies = [ "vergen", ] +[[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" @@ -1565,6 +1676,20 @@ version = "0.7.5" 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" @@ -1593,6 +1718,60 @@ dependencies = [ "windows-sys 0.48.0", ] +[[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" @@ -1746,9 +1925,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "snow" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" dependencies = [ "aes-gcm", "blake2", @@ -1794,6 +1973,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[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" @@ -1986,6 +2171,17 @@ dependencies = [ "tokio", ] +[[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" @@ -2068,7 +2264,7 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 1.9.3", "pin-project", "pin-project-lite", "rand", @@ -2209,14 +2405,20 @@ dependencies = [ [[package]] name = "universal-hash" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "generic-array", + "crypto-common", "subtle", ] +[[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" @@ -2457,6 +2659,12 @@ version = "0.48.5" 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" diff --git a/Cargo.toml b/Cargo.toml index 1311f889..44fcf97a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rathole" -version = "0.4.9" +version = "0.5.0" edition = "2021" authors = ["Yujia Qiao "] description = "A reverse proxy for NAT traversal" @@ -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,24 +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/README.md b/README.md index 18e46aff..25cf97f1 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Create `server.toml` with the following content and accommodate it to your needs bind_addr = "0.0.0.0:2333" # `2333` specifies the port that rathole listens for clients [server.services.my_nas_ssh] -token = "use_a_secret_that_only_you_know" # Token that is used to authenticate the client for the service. Change to a arbitrary value. +token = "use_a_secret_that_only_you_know" # Token that is used to authenticate the client for the service. Change to an arbitrary value. bind_addr = "0.0.0.0:5202" # `5202` specifies the port that exposes `my_nas_ssh` to the Internet ``` diff --git a/docs/build-guide.md b/docs/build-guide.md index d9750688..2adf6c22 100644 --- a/docs/build-guide.md +++ b/docs/build-guide.md @@ -1,30 +1,54 @@ # Build Guide + This is for those who want to build `rathole` themselves, possibly because the need of latest features or the minimal binary size. ## Build + To use default build settings, run: -``` + +```sh cargo build --release ``` -## Customize the build +You may need to pre-install [openssl](https://docs.rs/openssl/latest/openssl/index.html) dependencies in Unix-like systems. + +## Customize the Build + `rathole` comes with lots of *crate features* that determine whether a certain feature will be compiled or not. Supported features can be checked out in `[features]` of [Cargo.toml](../Cargo.toml). For example, to build `rathole` with the `client` and `noise` feature: -``` + +```sh cargo build --release --no-default-features --features client,noise ``` +## Rustls Support + +`rathole` provides optional `rustls` support. It's an almost drop-in replacement of `native-tls` support. (See [Transport](transport.md) for more information.) + +To enable this, disable the default features and enable `rustls` feature. And for websocket feature, enable `websocket-rustls` feature as well. + +You can also use command line option for this. For example, to replace all default features with `rustls`: + +```sh +cargo build --release --no-default-features --features server,client,rustls,noise,websocket-rustls,hot-reload +``` + +Feature `rustls` and `websocket-rustls` cannot be enabled with `native-tls` and `websocket-native-tls` at the same time, as they are mutually exclusive. Enabling both will result in a compile error. + +(Note that default features contains `native-tls` and `websocket-native-tls`.) + ## Minimalize the binary 1. Build with the `minimal` profile -The `release` build profile optimize for the program running time, not the binary size. +The `release` build profile optimize for the program running time, not the binary size. However, the `minimal` profile enables lots of optimization for the binary size to produce a much smaller binary. For example, to build `rathole` with `client` feature with the `minimal` profile: -``` + +```sh cargo build --profile minimal --no-default-features --features client ``` @@ -33,7 +57,8 @@ cargo build --profile minimal --no-default-features --features client The binary that step 1 produces can be even smaller, by using `strip` and `upx` to remove the symbols and compress the binary. Like: -``` + +```sh strip rathole upx --best --lzma rathole ``` diff --git a/docs/internals.md b/docs/internals.md index b0ede5a3..0f630836 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -30,5 +30,5 @@ When `rathole` starts in the client mode, it creates connections to `server.comm When a control channel starts, the server challenge the client by a nonce, the client is required to authenticate as the service it wants to represent. Then the forwarding of that service is set up. -When the server accepts a connection on a service's `bind_port`, it sends a control command to the client via the corresponding contorl channel. Then the client connects to the server to create a data channel. In this way, a forwarding is set up. The server also creates a few data channels in advance to improve the latency. +When the server accepts a connection on a service's `bind_port`, it sends a control command to the client via the corresponding control channel. Then the client connects to the server to create a data channel. In this way, a forwarding is set up. The server also creates a few data channels in advance to improve the latency. diff --git a/docs/transport.md b/docs/transport.md index a66bc7d6..c59f1dbe 100644 --- a/docs/transport.md +++ b/docs/transport.md @@ -3,21 +3,27 @@ By default, `rathole` forwards traffic as it is. Different options can be enabled to secure the traffic. ## TLS + Checkout the [example](../examples/tls) + ### Client + Normally, a self-signed certificate is used. In this case, the client needs to trust the CA. `trusted_root` is the path to the root CA's certificate PEM file. `hostname` is the hostname that the client used to validate aginst the certificate that the server presents. Note that it does not have to be the same with the `remote_addr` in `[client]`. -``` + +```toml [client.transport.tls] trusted_root = "example/tls/rootCA.crt" hostname = "localhost" ``` ### Server + PKCS#12 archives are needed to run the server. It can be created using openssl like: -``` + +```sh openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile ca_chain_certs.crt ``` @@ -29,8 +35,22 @@ Aruguments are: Creating self-signed certificate with one's own CA is a non-trival task. However, a script is provided under tls example folder for reference. +### Rustls Support + +`rathole` provides optional `rustls` support. [Build Guide](build-guide.md) demostrated this. + +One difference is that, the crate we use for loading PKCS#12 archives can only handle limited types of PBE algorithms. We only support PKCS#12 archives that they (crate `p12`) support. So we need to specify the legacy format (openssl 1.x format) when creating the PKCS#12 archive. + +In short, the command used with openssl 3 to create the PKCS#12 archive with `rustls` support is: + +```sh +openssl pkcs12 -export -out identity.pfx -inkey server.key -in server.crt -certfile ca_chain_certs.crt -legacy +``` + ## Noise Protocol + ### Quickstart for the Noise Protocl + In one word, the [Noise Protocol](http://noiseprotocol.org/noise.html) is a lightweigt, easy to configure and drop-in replacement of TLS. No need to create a self-sign certificate to secure the connection. `rathole` comes with a reasonable default configuration for noise protocol. You can a glimpse of the minimal [example](../examples/noise_nk) for how it will look like. @@ -38,12 +58,14 @@ In one word, the [Noise Protocol](http://noiseprotocol.org/noise.html) is a ligh The default noise protocol that `rathole` uses, which is `Noise_NK_25519_ChaChaPoly_BLAKE2s`, providing the authentication of the server, just like TLS with properly configured certificates. So MITM is no more a problem. To use it, a X25519 keypair is needed. + #### Generate a Keypair 1. Run `rathole --genkey`, which will generate a keypair using the default X25519 algorithm. It emits: -``` + +```sh $ rathole --genkey Private Key: cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q= @@ -51,11 +73,13 @@ cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q= Public Key: GQYTKSbWLBUSZiGfdWPSgek9yoOuaiwGD/GIX8Z1kkE= ``` + (WARNING: Don't use the keypair from the Internet, including this one) 2. The server should keep the private key to identify itself. And the client should keep the public key, which is used to verify whether the peer is the authentic server. So relevant snippets of configuration are: + ```toml # Client Side Configuration [client.transport] @@ -73,9 +97,11 @@ local_private_key = "cQ/vwIqNPJZmuM/OikglzBo/+jlYGrOt9i0k5h5vn1Q=" Then `rathole` will run under the protection of the Noise Protocol. ## Specifying the Pattern of Noise Protocol + The default configuration of Noise Protocol that comes with `rathole` satifies most use cases, which is described above. But there're other patterns that can be useful. ### No Authentication + This configuration provides encryption of the traffic but provides no authentication, which means it's vulnerable to MITM attack, but is resistent to the sniffing and replay attack. If MITM attack is not one of the concerns, this is more convenient to use. ```toml @@ -107,6 +133,7 @@ remote_public_key = "server-pub-key-here" ### Other Patterns To find out which pattern to use, refer to: + - [7.5. Interactive handshake patterns (fundamental)](https://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) - [8. Protocol names and modifiers](https://noiseprotocol.org/noise.html#protocol-names-and-modifiers) diff --git a/examples/tls/create_self_signed_cert.sh b/examples/tls/create_self_signed_cert.sh index e110a1f4..6ab9db37 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 dfc5160d..2041157a 100644 Binary files a/examples/tls/identity.pfx and b/examples/tls/identity.pfx differ diff --git a/examples/tls/rootCA.crt b/examples/tls/rootCA.crt index f3ed90f3..b48e63d0 100644 --- a/examples/tls/rootCA.crt +++ b/examples/tls/rootCA.crt @@ -1,20 +1,20 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/src/client.rs b/src/client.rs index 95a5a749..2564869c 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::::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::::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 b795932f..a292969f 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -1,8 +1,9 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use async_http_proxy::{http_connect_tokio, http_connect_tokio_with_basic_auth}; use backoff::{backoff::Backoff, Notify}; use socket2::{SockRef, TcpKeepalive}; use std::{future::Future, net::SocketAddr, time::Duration}; +use tokio::io::{AsyncWrite, AsyncWriteExt}; use tokio::{ net::{lookup_host, TcpStream, ToSocketAddrs, UdpSocket}, sync::broadcast, @@ -42,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(addr: A) -> Result { lookup_host(addr) .await? @@ -144,3 +153,14 @@ where } } } + +pub async fn write_and_flush(conn: &mut T, data: &[u8]) -> Result<()> +where + T: AsyncWrite + Unpin, +{ + conn.write_all(data) + .await + .with_context(|| "Failed to write data")?; + conn.flush().await.with_context(|| "Failed to flush data")?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 7fb2fa6d..65beb7f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,7 @@ pub async fn run(args: Cli, shutdown_rx: broadcast::Receiver) -> 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, service_update: mpsc::Receiver, -) { - 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/protocol.rs b/src/protocol.rs index 577c7323..955346ef 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -112,8 +112,7 @@ impl UdpTraffic { } pub async fn read(reader: &mut T, hdr_len: u8) -> Result { - let mut buf = Vec::new(); - buf.resize(hdr_len as usize, 0); + let mut buf = vec![0; hdr_len as usize]; reader .read_exact(&mut buf) .await diff --git a/src/server.rs b/src/server.rs index a36e3c21..a4c49482 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,7 @@ use crate::config::{Config, ServerConfig, ServerServiceConfig, ServiceType, TransportType}; use crate::config_watcher::{ConfigChange, ServerServiceChange}; use crate::constants::{listen_backoff, UDP_BUFFER_SIZE}; -use crate::helper::retry_notify_with_deadline; +use crate::helper::{retry_notify_with_deadline, write_and_flush}; use crate::multi_map::MultiMap; use crate::protocol::Hello::{ControlChannelHello, DataChannelHello}; use crate::protocol::{ @@ -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::::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::::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") } } @@ -498,14 +498,9 @@ struct ControlChannel { impl ControlChannel { async fn write_and_flush(&mut self, data: &[u8]) -> Result<()> { - self.conn - .write_all(data) + write_and_flush(&mut self.conn, data) .await .with_context(|| "Failed to write control cmds")?; - self.conn - .flush() - .await - .with_context(|| "Failed to flush control cmds")?; Ok(()) } // Run a control channel @@ -640,7 +635,7 @@ async fn run_tcp_connection_pool( 'pool: while let Some(mut visitor) = visitor_rx.recv().await { loop { if let Some(mut ch) = data_ch_rx.recv().await { - if ch.write_all(&cmd).await.is_ok() { + if write_and_flush(&mut ch, &cmd).await.is_ok() { tokio::spawn(async move { let _ = copy_bidirectional(&mut ch, &mut visitor).await; }); @@ -690,7 +685,7 @@ async fn run_udp_connection_pool( .recv() .await .ok_or_else(|| anyhow!("No available data channels"))?; - conn.write_all(&cmd).await?; + write_and_flush(&mut conn, &cmd).await?; let mut buf = [0u8; UDP_BUFFER_SIZE]; loop { diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 38682a65..26d357fb 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 similarity index 91% rename from src/transport/tls.rs rename to src/transport/native_tls.rs index 918af04b..40afd50b 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 { + s.get_ref().get_ref().get_ref() +} diff --git a/src/transport/rustls.rs b/src/transport/rustls.rs new file mode 100644 index 00000000..3ca4704e --- /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, + tls_acceptor: Option, +} + +// 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> { + 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 = 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> { + 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; + + fn new(config: &TransportConfig) -> Result { + 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(&self, addr: A) -> Result { + 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 { + let conn = self.tls_acceptor.as_ref().unwrap().accept(conn).await?; + Ok(tokio_rustls::TlsStream::Server(conn)) + } + + async fn connect(&self, addr: &AddrMaybeCached) -> Result { + 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 { + &s.get_ref().0 +} diff --git a/src/transport/websocket.rs b/src/transport/websocket.rs index ec6177d1..228eff70 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 807f6fb2..7b5d408d 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,10 +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(any(feature = "native-tls", feature = "rustls"))] test("tests/for_tcp/tls_transport.toml", Type::Tcp).await?; + + #[cfg(feature = "noise")] test("tests/for_tcp/noise_transport.toml", Type::Tcp).await?; + + #[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(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] test("tests/for_tcp/websocket_tls_transport.toml", Type::Tcp).await?; Ok(()) @@ -87,10 +94,17 @@ async fn udp() -> Result<()> { test("tests/for_udp/tcp_transport.toml", Type::Udp).await?; // See above #[cfg(not(target_os = "macos"))] + #[cfg(any(feature = "native-tls", feature = "rustls"))] test("tests/for_udp/tls_transport.toml", Type::Udp).await?; + + #[cfg(feature = "noise")] test("tests/for_udp/noise_transport.toml", Type::Udp).await?; + + #[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(any(feature = "websocket-native-tls", feature = "websocket-rustls"))] test("tests/for_udp/websocket_tls_transport.toml", Type::Udp).await?; Ok(()) @@ -98,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);