├── .cargo └── config.toml ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode └── launch.json ├── Cargo.lock ├── Cargo.toml ├── README.md ├── gen_cert_and_key.sh ├── localhost.crt.pem ├── localhost.key.pem └── src ├── bin ├── rstunc.rs └── rstund.rs ├── client.rs ├── lib.rs ├── pem_util.rs ├── server.rs ├── tcp ├── mod.rs ├── tcp_server.rs └── tcp_tunnel.rs ├── tunnel_info_bridge.rs ├── tunnel_message.rs └── udp ├── mod.rs ├── udp_packet.rs ├── udp_server.rs └── udp_tunnel.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # works with NDK 28.0.13004108 2 | [target.aarch64-linux-android] 3 | ar = "/Users/neevek/Library/Android/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android-ar" 4 | linker = "/Users/neevek/Library/Android/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android35-clang" 5 | 6 | [target.armv7-linux-androideabi] 7 | ar = "/Users/neevek/Library/Android/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar" 8 | linker = "/Users/neevek/Library/Android/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/armv7a-linux-androideabi35-clang" 9 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'release/*' 7 | 8 | jobs: 9 | linux-x86_64: 10 | name: Linux x86_64 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | target: x86_64-unknown-linux-gnu 18 | override: true 19 | - run: cargo build --all-features --release && mkdir -p rstun-linux-x86_64 20 | && mv target/release/rstunc ./rstun-linux-x86_64/ 21 | && mv target/release/rstund ./rstun-linux-x86_64/ 22 | && tar zcf rstun-linux-x86_64.tar.gz ./rstun-linux-x86_64/* 23 | - name: Release 24 | uses: softprops/action-gh-release@v1 25 | if: startsWith(github.ref, 'refs/tags/') 26 | with: 27 | files: | 28 | rstun-linux-x86_64.tar.gz 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | linux-musl-x86_64: 33 | name: Linux musl x86_64 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v2 37 | - uses: actions-rs/toolchain@v1 38 | with: 39 | toolchain: stable 40 | target: x86_64-unknown-linux-musl 41 | override: true 42 | - run: sudo apt-get -y install musl-tools && rustup target add x86_64-unknown-linux-musl && cargo build --all-features --release --target x86_64-unknown-linux-musl 43 | && mkdir -p rstun-linux-musl-x86_64 44 | && mv target/x86_64-unknown-linux-musl/release/rstunc ./rstun-linux-musl-x86_64/ 45 | && mv target/x86_64-unknown-linux-musl/release/rstund ./rstun-linux-musl-x86_64/ 46 | && tar zcf rstun-linux-musl-x86_64.tar.gz ./rstun-linux-musl-x86_64/* 47 | - name: Release 48 | uses: softprops/action-gh-release@v1 49 | if: startsWith(github.ref, 'refs/tags/') 50 | with: 51 | files: | 52 | rstun-linux-musl-x86_64.tar.gz 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | 56 | windows-x86_64: 57 | name: Windows x86_64 58 | runs-on: windows-latest 59 | defaults: 60 | run: 61 | shell: bash 62 | steps: 63 | - uses: actions/checkout@v2 64 | - uses: actions-rs/toolchain@v1 65 | with: 66 | toolchain: stable 67 | target: x86_64-pc-windows-msvc 68 | override: true 69 | - run: cargo build --all-features --release && mkdir -p rstun-windows-x86_64 70 | && mv target/release/rstunc.exe ./rstun-windows-x86_64/ 71 | && mv target/release/rstund.exe ./rstun-windows-x86_64/ 72 | && 7z a rstun-windows-x86_64.zip ./rstun-windows-x86_64/* 73 | - name: Release 74 | uses: softprops/action-gh-release@v1 75 | if: startsWith(github.ref, 'refs/tags/') 76 | with: 77 | files: | 78 | rstun-windows-x86_64.zip 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | 82 | darwin-x86_64: 83 | name: Darwin x86_64 84 | runs-on: macos-latest 85 | steps: 86 | - uses: actions/checkout@v2 87 | - uses: actions-rs/toolchain@v1 88 | with: 89 | toolchain: stable 90 | target: x86_64-apple-darwin 91 | override: true 92 | - run: rustup target add x86_64-apple-darwin && cargo build --all-features --release --target x86_64-apple-darwin 93 | && mkdir -p rstun-darwin-x86_64 94 | && mv target/x86_64-apple-darwin/release/rstunc ./rstun-darwin-x86_64/ 95 | && mv target/x86_64-apple-darwin/release/rstund ./rstun-darwin-x86_64/ 96 | && zip rstun-darwin-x86_64.zip ./rstun-darwin-x86_64/* 97 | - name: Release 98 | uses: softprops/action-gh-release@v1 99 | if: startsWith(github.ref, 'refs/tags/') 100 | with: 101 | files: | 102 | rstun-darwin-x86_64.zip 103 | env: 104 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 105 | 106 | darwin-aarch64: 107 | name: Darwin Aarch64 108 | runs-on: macos-latest 109 | steps: 110 | - uses: actions/checkout@v2 111 | - uses: actions-rs/toolchain@v1 112 | with: 113 | toolchain: stable 114 | target: aarch64-apple-darwin 115 | override: true 116 | - run: rustup target add aarch64-apple-darwin && cargo build --all-features --release --target aarch64-apple-darwin 117 | && mkdir -p rstun-darwin-aarch64 118 | && mv target/aarch64-apple-darwin/release/rstunc ./rstun-darwin-aarch64/ 119 | && mv target/aarch64-apple-darwin/release/rstund ./rstun-darwin-aarch64/ 120 | && zip rstun-darwin-aarch64.zip ./rstun-darwin-aarch64/* 121 | - name: Release 122 | uses: softprops/action-gh-release@v1 123 | if: startsWith(github.ref, 'refs/tags/') 124 | with: 125 | files: | 126 | rstun-darwin-aarch64.zip 127 | env: 128 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 129 | 130 | linux-armv7: 131 | name: Linux ARMv7 132 | runs-on: ubuntu-latest 133 | steps: 134 | - uses: actions/checkout@v2 135 | - uses: actions-rs/toolchain@v1 136 | with: 137 | toolchain: stable 138 | target: armv7-unknown-linux-gnueabihf 139 | override: true 140 | - run: rustup target add armv7-unknown-linux-gnueabihf && cargo install cross --git https://github.com/cross-rs/cross 141 | && cross build --all-features --release --target armv7-unknown-linux-gnueabihf 142 | && mkdir -p rstun-linux-armv7 143 | && mv target/armv7-unknown-linux-gnueabihf/release/rstunc ./rstun-linux-armv7/ 144 | && mv target/armv7-unknown-linux-gnueabihf/release/rstund ./rstun-linux-armv7/ 145 | && tar zcf rstun-linux-armv7.tar.gz ./rstun-linux-armv7/* 146 | - name: Release 147 | uses: softprops/action-gh-release@v1 148 | if: startsWith(github.ref, 'refs/tags/') 149 | with: 150 | files: | 151 | rstun-linux-armv7.tar.gz 152 | env: 153 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 154 | 155 | linux-aarch64: 156 | name: Linux Aarch64 157 | runs-on: ubuntu-latest 158 | steps: 159 | - uses: actions/checkout@v2 160 | - uses: actions-rs/toolchain@v1 161 | with: 162 | toolchain: stable 163 | target: aarch64-unknown-linux-gnu 164 | override: true 165 | - run: rustup target add aarch64-unknown-linux-gnu && cargo install cross --git https://github.com/cross-rs/cross 166 | && cross build --all-features --release --target aarch64-unknown-linux-gnu 167 | && mkdir -p rstun-linux-aarch64 168 | && mv target/aarch64-unknown-linux-gnu/release/rstunc ./rstun-linux-aarch64/ 169 | && mv target/aarch64-unknown-linux-gnu/release/rstund ./rstun-linux-aarch64/ 170 | && tar zcf rstun-linux-aarch64.tar.gz ./rstun-linux-aarch64/* 171 | - name: Release 172 | uses: softprops/action-gh-release@v1 173 | if: startsWith(github.ref, 'refs/tags/') 174 | with: 175 | files: | 176 | rstun-linux-aarch64.tar.gz 177 | env: 178 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 179 | 180 | linux-musl-aarch64: 181 | name: Linux musl Aarch64 182 | runs-on: ubuntu-latest 183 | steps: 184 | - uses: actions/checkout@v2 185 | - uses: actions-rs/toolchain@v1 186 | with: 187 | toolchain: stable 188 | target: aarch64-unknown-linux-musl 189 | override: true 190 | - run: sudo apt-get -y install musl-tools && rustup target add aarch64-unknown-linux-musl 191 | - run: cargo install cross --git https://github.com/cross-rs/cross 192 | - run: cross build --all-features --release --target aarch64-unknown-linux-musl 193 | - run: mkdir -p rstun-linux-musl-aarch64 194 | - run: mv target/aarch64-unknown-linux-musl/release/rstunc ./rstun-linux-musl-aarch64/ 195 | - run: mv target/aarch64-unknown-linux-musl/release/rstund ./rstun-linux-musl-aarch64/ 196 | - run: tar zcf rstun-linux-musl-aarch64.tar.gz ./rstun-linux-musl-aarch64/* 197 | - name: Release 198 | uses: softprops/action-gh-release@v1 199 | if: startsWith(github.ref, 'refs/tags/') 200 | with: 201 | files: | 202 | rstun-linux-musl-aarch64.tar.gz 203 | env: 204 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 205 | 206 | 207 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug rstunc", 6 | "type": "codelldb", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/target/debug/rstunc", 9 | "args": [ 10 | "-mOUT", "-a127.0.0.1:9701", "-p1234", "-t0.0.0.0:9500^ANY", "-lD" 11 | ], 12 | "cwd": "${workspaceFolder}", 13 | "sourceLanguages": ["rust"], 14 | "stopOnEntry": false, 15 | "console": "integratedTerminal" 16 | }, 17 | { 18 | "name": "Debug rstund", 19 | "type": "codelldb", 20 | "request": "launch", 21 | "program": "${workspaceFolder}/target/debug/rstund", 22 | "args": [ 23 | "-a127.0.0.1:9701", "-p1234", "-lD" 24 | ], 25 | "cwd": "${workspaceFolder}", 26 | "sourceLanguages": ["rust"], 27 | "stopOnEntry": false, 28 | "console": "integratedTerminal" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_log-sys" 37 | version = "0.3.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" 40 | 41 | [[package]] 42 | name = "android_logger" 43 | version = "0.13.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f" 46 | dependencies = [ 47 | "android_log-sys", 48 | "env_logger", 49 | "log", 50 | "once_cell", 51 | ] 52 | 53 | [[package]] 54 | name = "android_logger" 55 | version = "0.15.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "f6f39be698127218cca460cb624878c9aa4e2b47dba3b277963d2bf00bad263b" 58 | dependencies = [ 59 | "android_log-sys", 60 | "env_filter", 61 | "log", 62 | ] 63 | 64 | [[package]] 65 | name = "android_system_properties" 66 | version = "0.1.5" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 69 | dependencies = [ 70 | "libc", 71 | ] 72 | 73 | [[package]] 74 | name = "anstream" 75 | version = "0.6.18" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 78 | dependencies = [ 79 | "anstyle", 80 | "anstyle-parse", 81 | "anstyle-query", 82 | "anstyle-wincon", 83 | "colorchoice", 84 | "is_terminal_polyfill", 85 | "utf8parse", 86 | ] 87 | 88 | [[package]] 89 | name = "anstyle" 90 | version = "1.0.10" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 93 | 94 | [[package]] 95 | name = "anstyle-parse" 96 | version = "0.2.6" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 99 | dependencies = [ 100 | "utf8parse", 101 | ] 102 | 103 | [[package]] 104 | name = "anstyle-query" 105 | version = "1.1.2" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 108 | dependencies = [ 109 | "windows-sys 0.59.0", 110 | ] 111 | 112 | [[package]] 113 | name = "anstyle-wincon" 114 | version = "3.0.8" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" 117 | dependencies = [ 118 | "anstyle", 119 | "once_cell_polyfill", 120 | "windows-sys 0.59.0", 121 | ] 122 | 123 | [[package]] 124 | name = "anyhow" 125 | version = "1.0.98" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 128 | 129 | [[package]] 130 | name = "asn1-rs" 131 | version = "0.7.1" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" 134 | dependencies = [ 135 | "asn1-rs-derive", 136 | "asn1-rs-impl", 137 | "displaydoc", 138 | "nom", 139 | "num-traits", 140 | "rusticata-macros", 141 | "thiserror 2.0.12", 142 | "time", 143 | ] 144 | 145 | [[package]] 146 | name = "asn1-rs-derive" 147 | version = "0.6.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" 150 | dependencies = [ 151 | "proc-macro2", 152 | "quote", 153 | "syn", 154 | "synstructure", 155 | ] 156 | 157 | [[package]] 158 | name = "asn1-rs-impl" 159 | version = "0.2.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" 162 | dependencies = [ 163 | "proc-macro2", 164 | "quote", 165 | "syn", 166 | ] 167 | 168 | [[package]] 169 | name = "async-trait" 170 | version = "0.1.88" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 173 | dependencies = [ 174 | "proc-macro2", 175 | "quote", 176 | "syn", 177 | ] 178 | 179 | [[package]] 180 | name = "autocfg" 181 | version = "1.4.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 184 | 185 | [[package]] 186 | name = "backon" 187 | version = "1.5.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "302eaff5357a264a2c42f127ecb8bac761cf99749fc3dc95677e2743991f99e7" 190 | dependencies = [ 191 | "fastrand", 192 | "gloo-timers", 193 | "tokio", 194 | ] 195 | 196 | [[package]] 197 | name = "backtrace" 198 | version = "0.3.75" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 201 | dependencies = [ 202 | "addr2line", 203 | "cfg-if", 204 | "libc", 205 | "miniz_oxide", 206 | "object", 207 | "rustc-demangle", 208 | "windows-targets 0.52.6", 209 | ] 210 | 211 | [[package]] 212 | name = "base64" 213 | version = "0.21.7" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 216 | 217 | [[package]] 218 | name = "base64" 219 | version = "0.22.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 222 | 223 | [[package]] 224 | name = "bincode" 225 | version = "2.0.1" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" 228 | dependencies = [ 229 | "bincode_derive", 230 | "serde", 231 | "unty", 232 | ] 233 | 234 | [[package]] 235 | name = "bincode_derive" 236 | version = "2.0.1" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" 239 | dependencies = [ 240 | "virtue", 241 | ] 242 | 243 | [[package]] 244 | name = "bitflags" 245 | version = "2.9.1" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 248 | 249 | [[package]] 250 | name = "bumpalo" 251 | version = "3.17.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 254 | 255 | [[package]] 256 | name = "byte-pool" 257 | version = "0.2.3" 258 | source = "git+https://github.com/neevek/byte-pool#e3a193a24da8e8f7dfa42c31b418f9aee7de1cb0" 259 | dependencies = [ 260 | "crossbeam-queue", 261 | "stable_deref_trait", 262 | ] 263 | 264 | [[package]] 265 | name = "bytemuck" 266 | version = "1.23.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" 269 | 270 | [[package]] 271 | name = "bytes" 272 | version = "1.10.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 275 | 276 | [[package]] 277 | name = "cc" 278 | version = "1.2.25" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" 281 | dependencies = [ 282 | "shlex", 283 | ] 284 | 285 | [[package]] 286 | name = "cesu8" 287 | version = "1.1.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 290 | 291 | [[package]] 292 | name = "cfg-if" 293 | version = "1.0.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 296 | 297 | [[package]] 298 | name = "cfg_aliases" 299 | version = "0.2.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 302 | 303 | [[package]] 304 | name = "chrono" 305 | version = "0.4.41" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 308 | dependencies = [ 309 | "android-tzdata", 310 | "iana-time-zone", 311 | "js-sys", 312 | "num-traits", 313 | "wasm-bindgen", 314 | "windows-link", 315 | ] 316 | 317 | [[package]] 318 | name = "clap" 319 | version = "4.5.39" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" 322 | dependencies = [ 323 | "clap_builder", 324 | "clap_derive", 325 | ] 326 | 327 | [[package]] 328 | name = "clap_builder" 329 | version = "4.5.39" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" 332 | dependencies = [ 333 | "anstream", 334 | "anstyle", 335 | "clap_lex", 336 | "strsim", 337 | ] 338 | 339 | [[package]] 340 | name = "clap_derive" 341 | version = "4.5.32" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 344 | dependencies = [ 345 | "heck", 346 | "proc-macro2", 347 | "quote", 348 | "syn", 349 | ] 350 | 351 | [[package]] 352 | name = "clap_lex" 353 | version = "0.7.4" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 356 | 357 | [[package]] 358 | name = "colorchoice" 359 | version = "1.0.3" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 362 | 363 | [[package]] 364 | name = "combine" 365 | version = "4.6.7" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 368 | dependencies = [ 369 | "bytes", 370 | "memchr", 371 | ] 372 | 373 | [[package]] 374 | name = "core-foundation" 375 | version = "0.10.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 378 | dependencies = [ 379 | "core-foundation-sys", 380 | "libc", 381 | ] 382 | 383 | [[package]] 384 | name = "core-foundation-sys" 385 | version = "0.8.7" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 388 | 389 | [[package]] 390 | name = "crossbeam-queue" 391 | version = "0.3.12" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 394 | dependencies = [ 395 | "crossbeam-utils", 396 | ] 397 | 398 | [[package]] 399 | name = "crossbeam-utils" 400 | version = "0.8.21" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 403 | 404 | [[package]] 405 | name = "ctrlc" 406 | version = "3.4.7" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" 409 | dependencies = [ 410 | "nix", 411 | "windows-sys 0.59.0", 412 | ] 413 | 414 | [[package]] 415 | name = "dashmap" 416 | version = "6.1.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 419 | dependencies = [ 420 | "cfg-if", 421 | "crossbeam-utils", 422 | "hashbrown", 423 | "lock_api", 424 | "once_cell", 425 | "parking_lot_core", 426 | ] 427 | 428 | [[package]] 429 | name = "data-encoding" 430 | version = "2.9.0" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 433 | 434 | [[package]] 435 | name = "der-parser" 436 | version = "10.0.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" 439 | dependencies = [ 440 | "asn1-rs", 441 | "displaydoc", 442 | "nom", 443 | "num-bigint", 444 | "num-traits", 445 | "rusticata-macros", 446 | ] 447 | 448 | [[package]] 449 | name = "deranged" 450 | version = "0.4.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 453 | dependencies = [ 454 | "powerfmt", 455 | ] 456 | 457 | [[package]] 458 | name = "displaydoc" 459 | version = "0.2.5" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 462 | dependencies = [ 463 | "proc-macro2", 464 | "quote", 465 | "syn", 466 | ] 467 | 468 | [[package]] 469 | name = "enum-as-inner" 470 | version = "0.6.1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 473 | dependencies = [ 474 | "heck", 475 | "proc-macro2", 476 | "quote", 477 | "syn", 478 | ] 479 | 480 | [[package]] 481 | name = "env_filter" 482 | version = "0.1.3" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 485 | dependencies = [ 486 | "log", 487 | "regex", 488 | ] 489 | 490 | [[package]] 491 | name = "env_logger" 492 | version = "0.10.2" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 495 | dependencies = [ 496 | "humantime", 497 | "is-terminal", 498 | "log", 499 | "regex", 500 | "termcolor", 501 | ] 502 | 503 | [[package]] 504 | name = "fastbloom" 505 | version = "0.9.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "27cea6e7f512d43b098939ff4d5a5d6fe3db07971e1d05176fe26c642d33f5b8" 508 | dependencies = [ 509 | "getrandom 0.3.3", 510 | "rand 0.9.1", 511 | "siphasher", 512 | "wide", 513 | ] 514 | 515 | [[package]] 516 | name = "fastrand" 517 | version = "2.3.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 520 | 521 | [[package]] 522 | name = "form_urlencoded" 523 | version = "1.2.1" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 526 | dependencies = [ 527 | "percent-encoding", 528 | ] 529 | 530 | [[package]] 531 | name = "futures-channel" 532 | version = "0.3.31" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 535 | dependencies = [ 536 | "futures-core", 537 | ] 538 | 539 | [[package]] 540 | name = "futures-core" 541 | version = "0.3.31" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 544 | 545 | [[package]] 546 | name = "futures-io" 547 | version = "0.3.31" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 550 | 551 | [[package]] 552 | name = "futures-macro" 553 | version = "0.3.31" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 556 | dependencies = [ 557 | "proc-macro2", 558 | "quote", 559 | "syn", 560 | ] 561 | 562 | [[package]] 563 | name = "futures-task" 564 | version = "0.3.31" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 567 | 568 | [[package]] 569 | name = "futures-util" 570 | version = "0.3.31" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 573 | dependencies = [ 574 | "futures-core", 575 | "futures-macro", 576 | "futures-task", 577 | "pin-project-lite", 578 | "pin-utils", 579 | "slab", 580 | ] 581 | 582 | [[package]] 583 | name = "getrandom" 584 | version = "0.2.16" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 587 | dependencies = [ 588 | "cfg-if", 589 | "js-sys", 590 | "libc", 591 | "wasi 0.11.0+wasi-snapshot-preview1", 592 | "wasm-bindgen", 593 | ] 594 | 595 | [[package]] 596 | name = "getrandom" 597 | version = "0.3.3" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 600 | dependencies = [ 601 | "cfg-if", 602 | "js-sys", 603 | "libc", 604 | "r-efi", 605 | "wasi 0.14.2+wasi-0.2.4", 606 | "wasm-bindgen", 607 | ] 608 | 609 | [[package]] 610 | name = "gimli" 611 | version = "0.31.1" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 614 | 615 | [[package]] 616 | name = "gloo-timers" 617 | version = "0.3.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 620 | dependencies = [ 621 | "futures-channel", 622 | "futures-core", 623 | "js-sys", 624 | "wasm-bindgen", 625 | ] 626 | 627 | [[package]] 628 | name = "hashbrown" 629 | version = "0.14.5" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 632 | 633 | [[package]] 634 | name = "heck" 635 | version = "0.5.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 638 | 639 | [[package]] 640 | name = "hermit-abi" 641 | version = "0.5.1" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" 644 | 645 | [[package]] 646 | name = "humantime" 647 | version = "2.2.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" 650 | 651 | [[package]] 652 | name = "iana-time-zone" 653 | version = "0.1.63" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 656 | dependencies = [ 657 | "android_system_properties", 658 | "core-foundation-sys", 659 | "iana-time-zone-haiku", 660 | "js-sys", 661 | "log", 662 | "wasm-bindgen", 663 | "windows-core", 664 | ] 665 | 666 | [[package]] 667 | name = "iana-time-zone-haiku" 668 | version = "0.1.2" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 671 | dependencies = [ 672 | "cc", 673 | ] 674 | 675 | [[package]] 676 | name = "icu_collections" 677 | version = "2.0.0" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 680 | dependencies = [ 681 | "displaydoc", 682 | "potential_utf", 683 | "yoke", 684 | "zerofrom", 685 | "zerovec", 686 | ] 687 | 688 | [[package]] 689 | name = "icu_locale_core" 690 | version = "2.0.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 693 | dependencies = [ 694 | "displaydoc", 695 | "litemap", 696 | "tinystr", 697 | "writeable", 698 | "zerovec", 699 | ] 700 | 701 | [[package]] 702 | name = "icu_normalizer" 703 | version = "2.0.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 706 | dependencies = [ 707 | "displaydoc", 708 | "icu_collections", 709 | "icu_normalizer_data", 710 | "icu_properties", 711 | "icu_provider", 712 | "smallvec", 713 | "zerovec", 714 | ] 715 | 716 | [[package]] 717 | name = "icu_normalizer_data" 718 | version = "2.0.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 721 | 722 | [[package]] 723 | name = "icu_properties" 724 | version = "2.0.1" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 727 | dependencies = [ 728 | "displaydoc", 729 | "icu_collections", 730 | "icu_locale_core", 731 | "icu_properties_data", 732 | "icu_provider", 733 | "potential_utf", 734 | "zerotrie", 735 | "zerovec", 736 | ] 737 | 738 | [[package]] 739 | name = "icu_properties_data" 740 | version = "2.0.1" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 743 | 744 | [[package]] 745 | name = "icu_provider" 746 | version = "2.0.0" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 749 | dependencies = [ 750 | "displaydoc", 751 | "icu_locale_core", 752 | "stable_deref_trait", 753 | "tinystr", 754 | "writeable", 755 | "yoke", 756 | "zerofrom", 757 | "zerotrie", 758 | "zerovec", 759 | ] 760 | 761 | [[package]] 762 | name = "idna" 763 | version = "0.4.0" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 766 | dependencies = [ 767 | "unicode-bidi", 768 | "unicode-normalization", 769 | ] 770 | 771 | [[package]] 772 | name = "idna" 773 | version = "1.0.3" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 776 | dependencies = [ 777 | "idna_adapter", 778 | "smallvec", 779 | "utf8_iter", 780 | ] 781 | 782 | [[package]] 783 | name = "idna_adapter" 784 | version = "1.2.1" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 787 | dependencies = [ 788 | "icu_normalizer", 789 | "icu_properties", 790 | ] 791 | 792 | [[package]] 793 | name = "ipconfig" 794 | version = "0.3.2" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 797 | dependencies = [ 798 | "socket2", 799 | "widestring", 800 | "windows-sys 0.48.0", 801 | "winreg", 802 | ] 803 | 804 | [[package]] 805 | name = "ipnet" 806 | version = "2.11.0" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 809 | 810 | [[package]] 811 | name = "is-terminal" 812 | version = "0.4.16" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" 815 | dependencies = [ 816 | "hermit-abi", 817 | "libc", 818 | "windows-sys 0.59.0", 819 | ] 820 | 821 | [[package]] 822 | name = "is_terminal_polyfill" 823 | version = "1.70.1" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 826 | 827 | [[package]] 828 | name = "itoa" 829 | version = "1.0.15" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 832 | 833 | [[package]] 834 | name = "jni" 835 | version = "0.21.1" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 838 | dependencies = [ 839 | "cesu8", 840 | "cfg-if", 841 | "combine", 842 | "jni-sys", 843 | "log", 844 | "thiserror 1.0.69", 845 | "walkdir", 846 | "windows-sys 0.45.0", 847 | ] 848 | 849 | [[package]] 850 | name = "jni-sys" 851 | version = "0.3.0" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 854 | 855 | [[package]] 856 | name = "js-sys" 857 | version = "0.3.77" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 860 | dependencies = [ 861 | "once_cell", 862 | "wasm-bindgen", 863 | ] 864 | 865 | [[package]] 866 | name = "lazy_static" 867 | version = "1.5.0" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 870 | 871 | [[package]] 872 | name = "libc" 873 | version = "0.2.172" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 876 | 877 | [[package]] 878 | name = "linked-hash-map" 879 | version = "0.5.6" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 882 | 883 | [[package]] 884 | name = "litemap" 885 | version = "0.8.0" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 888 | 889 | [[package]] 890 | name = "lock_api" 891 | version = "0.4.13" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 894 | dependencies = [ 895 | "autocfg", 896 | "scopeguard", 897 | ] 898 | 899 | [[package]] 900 | name = "log" 901 | version = "0.4.27" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 904 | 905 | [[package]] 906 | name = "lru-cache" 907 | version = "0.1.2" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 910 | dependencies = [ 911 | "linked-hash-map", 912 | ] 913 | 914 | [[package]] 915 | name = "lru-slab" 916 | version = "0.1.2" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 919 | 920 | [[package]] 921 | name = "memchr" 922 | version = "2.7.4" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 925 | 926 | [[package]] 927 | name = "minimal-lexical" 928 | version = "0.2.1" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 931 | 932 | [[package]] 933 | name = "miniz_oxide" 934 | version = "0.8.8" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 937 | dependencies = [ 938 | "adler2", 939 | ] 940 | 941 | [[package]] 942 | name = "mio" 943 | version = "1.0.4" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 946 | dependencies = [ 947 | "libc", 948 | "wasi 0.11.0+wasi-snapshot-preview1", 949 | "windows-sys 0.59.0", 950 | ] 951 | 952 | [[package]] 953 | name = "nix" 954 | version = "0.30.1" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 957 | dependencies = [ 958 | "bitflags", 959 | "cfg-if", 960 | "cfg_aliases", 961 | "libc", 962 | ] 963 | 964 | [[package]] 965 | name = "nom" 966 | version = "7.1.3" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 969 | dependencies = [ 970 | "memchr", 971 | "minimal-lexical", 972 | ] 973 | 974 | [[package]] 975 | name = "num-bigint" 976 | version = "0.4.6" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 979 | dependencies = [ 980 | "num-integer", 981 | "num-traits", 982 | ] 983 | 984 | [[package]] 985 | name = "num-conv" 986 | version = "0.1.0" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 989 | 990 | [[package]] 991 | name = "num-integer" 992 | version = "0.1.46" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 995 | dependencies = [ 996 | "num-traits", 997 | ] 998 | 999 | [[package]] 1000 | name = "num-traits" 1001 | version = "0.2.19" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1004 | dependencies = [ 1005 | "autocfg", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "num_cpus" 1010 | version = "1.17.0" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" 1013 | dependencies = [ 1014 | "hermit-abi", 1015 | "libc", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "object" 1020 | version = "0.36.7" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1023 | dependencies = [ 1024 | "memchr", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "oid-registry" 1029 | version = "0.8.1" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" 1032 | dependencies = [ 1033 | "asn1-rs", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "once_cell" 1038 | version = "1.21.3" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1041 | 1042 | [[package]] 1043 | name = "once_cell_polyfill" 1044 | version = "1.70.1" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 1047 | 1048 | [[package]] 1049 | name = "openssl-probe" 1050 | version = "0.1.6" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1053 | 1054 | [[package]] 1055 | name = "parking_lot" 1056 | version = "0.12.4" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 1059 | dependencies = [ 1060 | "lock_api", 1061 | "parking_lot_core", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "parking_lot_core" 1066 | version = "0.9.11" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 1069 | dependencies = [ 1070 | "cfg-if", 1071 | "libc", 1072 | "redox_syscall", 1073 | "smallvec", 1074 | "windows-targets 0.52.6", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "pem" 1079 | version = "3.0.5" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" 1082 | dependencies = [ 1083 | "base64 0.22.1", 1084 | "serde", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "percent-encoding" 1089 | version = "2.3.1" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1092 | 1093 | [[package]] 1094 | name = "pin-project-lite" 1095 | version = "0.2.16" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1098 | 1099 | [[package]] 1100 | name = "pin-utils" 1101 | version = "0.1.0" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1104 | 1105 | [[package]] 1106 | name = "potential_utf" 1107 | version = "0.1.2" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 1110 | dependencies = [ 1111 | "zerovec", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "powerfmt" 1116 | version = "0.2.0" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1119 | 1120 | [[package]] 1121 | name = "ppv-lite86" 1122 | version = "0.2.21" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1125 | dependencies = [ 1126 | "zerocopy", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "pretty_env_logger" 1131 | version = "0.5.0" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" 1134 | dependencies = [ 1135 | "env_logger", 1136 | "log", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "proc-macro2" 1141 | version = "1.0.95" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1144 | dependencies = [ 1145 | "unicode-ident", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "quinn" 1150 | version = "0.11.8" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" 1153 | dependencies = [ 1154 | "bytes", 1155 | "cfg_aliases", 1156 | "pin-project-lite", 1157 | "quinn-proto", 1158 | "quinn-udp", 1159 | "rustc-hash", 1160 | "rustls 0.23.27", 1161 | "socket2", 1162 | "thiserror 2.0.12", 1163 | "tokio", 1164 | "tracing", 1165 | "web-time", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "quinn-proto" 1170 | version = "0.11.12" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" 1173 | dependencies = [ 1174 | "bytes", 1175 | "fastbloom", 1176 | "getrandom 0.3.3", 1177 | "lru-slab", 1178 | "rand 0.9.1", 1179 | "ring", 1180 | "rustc-hash", 1181 | "rustls 0.23.27", 1182 | "rustls-pki-types", 1183 | "rustls-platform-verifier 0.5.3", 1184 | "slab", 1185 | "thiserror 2.0.12", 1186 | "tinyvec", 1187 | "tracing", 1188 | "web-time", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "quinn-udp" 1193 | version = "0.5.12" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" 1196 | dependencies = [ 1197 | "cfg_aliases", 1198 | "libc", 1199 | "once_cell", 1200 | "socket2", 1201 | "tracing", 1202 | "windows-sys 0.59.0", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "quote" 1207 | version = "1.0.40" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1210 | dependencies = [ 1211 | "proc-macro2", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "r-efi" 1216 | version = "5.2.0" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1219 | 1220 | [[package]] 1221 | name = "rand" 1222 | version = "0.8.5" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1225 | dependencies = [ 1226 | "libc", 1227 | "rand_chacha 0.3.1", 1228 | "rand_core 0.6.4", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "rand" 1233 | version = "0.9.1" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" 1236 | dependencies = [ 1237 | "rand_chacha 0.9.0", 1238 | "rand_core 0.9.3", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "rand_chacha" 1243 | version = "0.3.1" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1246 | dependencies = [ 1247 | "ppv-lite86", 1248 | "rand_core 0.6.4", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "rand_chacha" 1253 | version = "0.9.0" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1256 | dependencies = [ 1257 | "ppv-lite86", 1258 | "rand_core 0.9.3", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "rand_core" 1263 | version = "0.6.4" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1266 | dependencies = [ 1267 | "getrandom 0.2.16", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "rand_core" 1272 | version = "0.9.3" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1275 | dependencies = [ 1276 | "getrandom 0.3.3", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "rcgen" 1281 | version = "0.13.2" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" 1284 | dependencies = [ 1285 | "pem", 1286 | "ring", 1287 | "rustls-pki-types", 1288 | "time", 1289 | "yasna", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "redox_syscall" 1294 | version = "0.5.12" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 1297 | dependencies = [ 1298 | "bitflags", 1299 | ] 1300 | 1301 | [[package]] 1302 | name = "regex" 1303 | version = "1.11.1" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1306 | dependencies = [ 1307 | "aho-corasick", 1308 | "memchr", 1309 | "regex-automata", 1310 | "regex-syntax", 1311 | ] 1312 | 1313 | [[package]] 1314 | name = "regex-automata" 1315 | version = "0.4.9" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1318 | dependencies = [ 1319 | "aho-corasick", 1320 | "memchr", 1321 | "regex-syntax", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "regex-syntax" 1326 | version = "0.8.5" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1329 | 1330 | [[package]] 1331 | name = "resolv-conf" 1332 | version = "0.7.4" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" 1335 | 1336 | [[package]] 1337 | name = "ring" 1338 | version = "0.17.14" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1341 | dependencies = [ 1342 | "cc", 1343 | "cfg-if", 1344 | "getrandom 0.2.16", 1345 | "libc", 1346 | "untrusted", 1347 | "windows-sys 0.52.0", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "rs-utilities" 1352 | version = "0.4.2" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "3992b92ab54cd07d1f0dd3112a4e1709d44373879223be17b5912590cb776c8e" 1355 | dependencies = [ 1356 | "android_logger 0.13.3", 1357 | "anyhow", 1358 | "async-trait", 1359 | "chrono", 1360 | "log", 1361 | "pretty_env_logger", 1362 | "rustls 0.21.12", 1363 | "tokio", 1364 | "trust-dns-resolver", 1365 | "webpki-roots", 1366 | ] 1367 | 1368 | [[package]] 1369 | name = "rstun" 1370 | version = "0.7.1" 1371 | dependencies = [ 1372 | "android_logger 0.15.0", 1373 | "anyhow", 1374 | "backon", 1375 | "bincode", 1376 | "byte-pool", 1377 | "bytes", 1378 | "chrono", 1379 | "clap", 1380 | "ctrlc", 1381 | "dashmap", 1382 | "enum-as-inner", 1383 | "futures-util", 1384 | "jni", 1385 | "lazy_static", 1386 | "log", 1387 | "num_cpus", 1388 | "pin-utils", 1389 | "pretty_env_logger", 1390 | "quinn", 1391 | "quinn-proto", 1392 | "rcgen", 1393 | "ring", 1394 | "rs-utilities", 1395 | "rustls 0.23.27", 1396 | "rustls-pemfile 2.2.0", 1397 | "rustls-platform-verifier 0.6.0", 1398 | "serde", 1399 | "serde_json", 1400 | "tokio", 1401 | "x509-parser", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "rustc-demangle" 1406 | version = "0.1.24" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1409 | 1410 | [[package]] 1411 | name = "rustc-hash" 1412 | version = "2.1.1" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 1415 | 1416 | [[package]] 1417 | name = "rusticata-macros" 1418 | version = "4.1.0" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" 1421 | dependencies = [ 1422 | "nom", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "rustls" 1427 | version = "0.21.12" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" 1430 | dependencies = [ 1431 | "log", 1432 | "ring", 1433 | "rustls-webpki 0.101.7", 1434 | "sct", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "rustls" 1439 | version = "0.23.27" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" 1442 | dependencies = [ 1443 | "once_cell", 1444 | "ring", 1445 | "rustls-pki-types", 1446 | "rustls-webpki 0.103.3", 1447 | "subtle", 1448 | "zeroize", 1449 | ] 1450 | 1451 | [[package]] 1452 | name = "rustls-native-certs" 1453 | version = "0.8.1" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" 1456 | dependencies = [ 1457 | "openssl-probe", 1458 | "rustls-pki-types", 1459 | "schannel", 1460 | "security-framework", 1461 | ] 1462 | 1463 | [[package]] 1464 | name = "rustls-pemfile" 1465 | version = "1.0.4" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1468 | dependencies = [ 1469 | "base64 0.21.7", 1470 | ] 1471 | 1472 | [[package]] 1473 | name = "rustls-pemfile" 1474 | version = "2.2.0" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1477 | dependencies = [ 1478 | "rustls-pki-types", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "rustls-pki-types" 1483 | version = "1.12.0" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 1486 | dependencies = [ 1487 | "web-time", 1488 | "zeroize", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "rustls-platform-verifier" 1493 | version = "0.5.3" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" 1496 | dependencies = [ 1497 | "core-foundation", 1498 | "core-foundation-sys", 1499 | "jni", 1500 | "log", 1501 | "once_cell", 1502 | "rustls 0.23.27", 1503 | "rustls-native-certs", 1504 | "rustls-platform-verifier-android", 1505 | "rustls-webpki 0.103.3", 1506 | "security-framework", 1507 | "security-framework-sys", 1508 | "webpki-root-certs 0.26.11", 1509 | "windows-sys 0.59.0", 1510 | ] 1511 | 1512 | [[package]] 1513 | name = "rustls-platform-verifier" 1514 | version = "0.6.0" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "eda84358ed17f1f354cf4b1909ad346e6c7bc2513e8c40eb08e0157aa13a9070" 1517 | dependencies = [ 1518 | "core-foundation", 1519 | "core-foundation-sys", 1520 | "jni", 1521 | "log", 1522 | "once_cell", 1523 | "rustls 0.23.27", 1524 | "rustls-native-certs", 1525 | "rustls-platform-verifier-android", 1526 | "rustls-webpki 0.103.3", 1527 | "security-framework", 1528 | "security-framework-sys", 1529 | "webpki-root-certs 1.0.0", 1530 | "windows-sys 0.59.0", 1531 | ] 1532 | 1533 | [[package]] 1534 | name = "rustls-platform-verifier-android" 1535 | version = "0.1.1" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" 1538 | 1539 | [[package]] 1540 | name = "rustls-webpki" 1541 | version = "0.101.7" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 1544 | dependencies = [ 1545 | "ring", 1546 | "untrusted", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "rustls-webpki" 1551 | version = "0.103.3" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" 1554 | dependencies = [ 1555 | "ring", 1556 | "rustls-pki-types", 1557 | "untrusted", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "rustversion" 1562 | version = "1.0.21" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 1565 | 1566 | [[package]] 1567 | name = "ryu" 1568 | version = "1.0.20" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1571 | 1572 | [[package]] 1573 | name = "safe_arch" 1574 | version = "0.7.4" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" 1577 | dependencies = [ 1578 | "bytemuck", 1579 | ] 1580 | 1581 | [[package]] 1582 | name = "same-file" 1583 | version = "1.0.6" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1586 | dependencies = [ 1587 | "winapi-util", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "schannel" 1592 | version = "0.1.27" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1595 | dependencies = [ 1596 | "windows-sys 0.59.0", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "scopeguard" 1601 | version = "1.2.0" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1604 | 1605 | [[package]] 1606 | name = "sct" 1607 | version = "0.7.1" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 1610 | dependencies = [ 1611 | "ring", 1612 | "untrusted", 1613 | ] 1614 | 1615 | [[package]] 1616 | name = "security-framework" 1617 | version = "3.2.0" 1618 | source = "registry+https://github.com/rust-lang/crates.io-index" 1619 | checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" 1620 | dependencies = [ 1621 | "bitflags", 1622 | "core-foundation", 1623 | "core-foundation-sys", 1624 | "libc", 1625 | "security-framework-sys", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "security-framework-sys" 1630 | version = "2.14.0" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1633 | dependencies = [ 1634 | "core-foundation-sys", 1635 | "libc", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "serde" 1640 | version = "1.0.219" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1643 | dependencies = [ 1644 | "serde_derive", 1645 | ] 1646 | 1647 | [[package]] 1648 | name = "serde_derive" 1649 | version = "1.0.219" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1652 | dependencies = [ 1653 | "proc-macro2", 1654 | "quote", 1655 | "syn", 1656 | ] 1657 | 1658 | [[package]] 1659 | name = "serde_json" 1660 | version = "1.0.140" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1663 | dependencies = [ 1664 | "itoa", 1665 | "memchr", 1666 | "ryu", 1667 | "serde", 1668 | ] 1669 | 1670 | [[package]] 1671 | name = "shlex" 1672 | version = "1.3.0" 1673 | source = "registry+https://github.com/rust-lang/crates.io-index" 1674 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1675 | 1676 | [[package]] 1677 | name = "signal-hook-registry" 1678 | version = "1.4.5" 1679 | source = "registry+https://github.com/rust-lang/crates.io-index" 1680 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 1681 | dependencies = [ 1682 | "libc", 1683 | ] 1684 | 1685 | [[package]] 1686 | name = "siphasher" 1687 | version = "1.0.1" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 1690 | 1691 | [[package]] 1692 | name = "slab" 1693 | version = "0.4.9" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1696 | dependencies = [ 1697 | "autocfg", 1698 | ] 1699 | 1700 | [[package]] 1701 | name = "smallvec" 1702 | version = "1.15.0" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1705 | 1706 | [[package]] 1707 | name = "socket2" 1708 | version = "0.5.10" 1709 | source = "registry+https://github.com/rust-lang/crates.io-index" 1710 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 1711 | dependencies = [ 1712 | "libc", 1713 | "windows-sys 0.52.0", 1714 | ] 1715 | 1716 | [[package]] 1717 | name = "stable_deref_trait" 1718 | version = "1.2.0" 1719 | source = "registry+https://github.com/rust-lang/crates.io-index" 1720 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1721 | 1722 | [[package]] 1723 | name = "strsim" 1724 | version = "0.11.1" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1727 | 1728 | [[package]] 1729 | name = "subtle" 1730 | version = "2.6.1" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1733 | 1734 | [[package]] 1735 | name = "syn" 1736 | version = "2.0.101" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 1739 | dependencies = [ 1740 | "proc-macro2", 1741 | "quote", 1742 | "unicode-ident", 1743 | ] 1744 | 1745 | [[package]] 1746 | name = "synstructure" 1747 | version = "0.13.2" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1750 | dependencies = [ 1751 | "proc-macro2", 1752 | "quote", 1753 | "syn", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "termcolor" 1758 | version = "1.4.1" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1761 | dependencies = [ 1762 | "winapi-util", 1763 | ] 1764 | 1765 | [[package]] 1766 | name = "thiserror" 1767 | version = "1.0.69" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1770 | dependencies = [ 1771 | "thiserror-impl 1.0.69", 1772 | ] 1773 | 1774 | [[package]] 1775 | name = "thiserror" 1776 | version = "2.0.12" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1779 | dependencies = [ 1780 | "thiserror-impl 2.0.12", 1781 | ] 1782 | 1783 | [[package]] 1784 | name = "thiserror-impl" 1785 | version = "1.0.69" 1786 | source = "registry+https://github.com/rust-lang/crates.io-index" 1787 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1788 | dependencies = [ 1789 | "proc-macro2", 1790 | "quote", 1791 | "syn", 1792 | ] 1793 | 1794 | [[package]] 1795 | name = "thiserror-impl" 1796 | version = "2.0.12" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1799 | dependencies = [ 1800 | "proc-macro2", 1801 | "quote", 1802 | "syn", 1803 | ] 1804 | 1805 | [[package]] 1806 | name = "time" 1807 | version = "0.3.41" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 1810 | dependencies = [ 1811 | "deranged", 1812 | "itoa", 1813 | "num-conv", 1814 | "powerfmt", 1815 | "serde", 1816 | "time-core", 1817 | "time-macros", 1818 | ] 1819 | 1820 | [[package]] 1821 | name = "time-core" 1822 | version = "0.1.4" 1823 | source = "registry+https://github.com/rust-lang/crates.io-index" 1824 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 1825 | 1826 | [[package]] 1827 | name = "time-macros" 1828 | version = "0.2.22" 1829 | source = "registry+https://github.com/rust-lang/crates.io-index" 1830 | checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" 1831 | dependencies = [ 1832 | "num-conv", 1833 | "time-core", 1834 | ] 1835 | 1836 | [[package]] 1837 | name = "tinystr" 1838 | version = "0.8.1" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1841 | dependencies = [ 1842 | "displaydoc", 1843 | "zerovec", 1844 | ] 1845 | 1846 | [[package]] 1847 | name = "tinyvec" 1848 | version = "1.9.0" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 1851 | dependencies = [ 1852 | "tinyvec_macros", 1853 | ] 1854 | 1855 | [[package]] 1856 | name = "tinyvec_macros" 1857 | version = "0.1.1" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1860 | 1861 | [[package]] 1862 | name = "tokio" 1863 | version = "1.45.1" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" 1866 | dependencies = [ 1867 | "backtrace", 1868 | "bytes", 1869 | "libc", 1870 | "mio", 1871 | "parking_lot", 1872 | "pin-project-lite", 1873 | "signal-hook-registry", 1874 | "socket2", 1875 | "tokio-macros", 1876 | "windows-sys 0.52.0", 1877 | ] 1878 | 1879 | [[package]] 1880 | name = "tokio-macros" 1881 | version = "2.5.0" 1882 | source = "registry+https://github.com/rust-lang/crates.io-index" 1883 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1884 | dependencies = [ 1885 | "proc-macro2", 1886 | "quote", 1887 | "syn", 1888 | ] 1889 | 1890 | [[package]] 1891 | name = "tokio-rustls" 1892 | version = "0.24.1" 1893 | source = "registry+https://github.com/rust-lang/crates.io-index" 1894 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 1895 | dependencies = [ 1896 | "rustls 0.21.12", 1897 | "tokio", 1898 | ] 1899 | 1900 | [[package]] 1901 | name = "tracing" 1902 | version = "0.1.41" 1903 | source = "registry+https://github.com/rust-lang/crates.io-index" 1904 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1905 | dependencies = [ 1906 | "log", 1907 | "pin-project-lite", 1908 | "tracing-attributes", 1909 | "tracing-core", 1910 | ] 1911 | 1912 | [[package]] 1913 | name = "tracing-attributes" 1914 | version = "0.1.28" 1915 | source = "registry+https://github.com/rust-lang/crates.io-index" 1916 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 1917 | dependencies = [ 1918 | "proc-macro2", 1919 | "quote", 1920 | "syn", 1921 | ] 1922 | 1923 | [[package]] 1924 | name = "tracing-core" 1925 | version = "0.1.33" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1928 | dependencies = [ 1929 | "once_cell", 1930 | ] 1931 | 1932 | [[package]] 1933 | name = "trust-dns-proto" 1934 | version = "0.23.2" 1935 | source = "registry+https://github.com/rust-lang/crates.io-index" 1936 | checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" 1937 | dependencies = [ 1938 | "async-trait", 1939 | "cfg-if", 1940 | "data-encoding", 1941 | "enum-as-inner", 1942 | "futures-channel", 1943 | "futures-io", 1944 | "futures-util", 1945 | "idna 0.4.0", 1946 | "ipnet", 1947 | "once_cell", 1948 | "rand 0.8.5", 1949 | "rustls 0.21.12", 1950 | "rustls-pemfile 1.0.4", 1951 | "rustls-webpki 0.101.7", 1952 | "smallvec", 1953 | "thiserror 1.0.69", 1954 | "tinyvec", 1955 | "tokio", 1956 | "tokio-rustls", 1957 | "tracing", 1958 | "url", 1959 | ] 1960 | 1961 | [[package]] 1962 | name = "trust-dns-resolver" 1963 | version = "0.23.2" 1964 | source = "registry+https://github.com/rust-lang/crates.io-index" 1965 | checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" 1966 | dependencies = [ 1967 | "cfg-if", 1968 | "futures-util", 1969 | "ipconfig", 1970 | "lru-cache", 1971 | "once_cell", 1972 | "parking_lot", 1973 | "rand 0.8.5", 1974 | "resolv-conf", 1975 | "rustls 0.21.12", 1976 | "smallvec", 1977 | "thiserror 1.0.69", 1978 | "tokio", 1979 | "tokio-rustls", 1980 | "tracing", 1981 | "trust-dns-proto", 1982 | "webpki-roots", 1983 | ] 1984 | 1985 | [[package]] 1986 | name = "unicode-bidi" 1987 | version = "0.3.18" 1988 | source = "registry+https://github.com/rust-lang/crates.io-index" 1989 | checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 1990 | 1991 | [[package]] 1992 | name = "unicode-ident" 1993 | version = "1.0.18" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1996 | 1997 | [[package]] 1998 | name = "unicode-normalization" 1999 | version = "0.1.24" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 2002 | dependencies = [ 2003 | "tinyvec", 2004 | ] 2005 | 2006 | [[package]] 2007 | name = "untrusted" 2008 | version = "0.9.0" 2009 | source = "registry+https://github.com/rust-lang/crates.io-index" 2010 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2011 | 2012 | [[package]] 2013 | name = "unty" 2014 | version = "0.0.4" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" 2017 | 2018 | [[package]] 2019 | name = "url" 2020 | version = "2.5.4" 2021 | source = "registry+https://github.com/rust-lang/crates.io-index" 2022 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2023 | dependencies = [ 2024 | "form_urlencoded", 2025 | "idna 1.0.3", 2026 | "percent-encoding", 2027 | ] 2028 | 2029 | [[package]] 2030 | name = "utf8_iter" 2031 | version = "1.0.4" 2032 | source = "registry+https://github.com/rust-lang/crates.io-index" 2033 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2034 | 2035 | [[package]] 2036 | name = "utf8parse" 2037 | version = "0.2.2" 2038 | source = "registry+https://github.com/rust-lang/crates.io-index" 2039 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2040 | 2041 | [[package]] 2042 | name = "virtue" 2043 | version = "0.0.18" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" 2046 | 2047 | [[package]] 2048 | name = "walkdir" 2049 | version = "2.5.0" 2050 | source = "registry+https://github.com/rust-lang/crates.io-index" 2051 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2052 | dependencies = [ 2053 | "same-file", 2054 | "winapi-util", 2055 | ] 2056 | 2057 | [[package]] 2058 | name = "wasi" 2059 | version = "0.11.0+wasi-snapshot-preview1" 2060 | source = "registry+https://github.com/rust-lang/crates.io-index" 2061 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2062 | 2063 | [[package]] 2064 | name = "wasi" 2065 | version = "0.14.2+wasi-0.2.4" 2066 | source = "registry+https://github.com/rust-lang/crates.io-index" 2067 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 2068 | dependencies = [ 2069 | "wit-bindgen-rt", 2070 | ] 2071 | 2072 | [[package]] 2073 | name = "wasm-bindgen" 2074 | version = "0.2.100" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2077 | dependencies = [ 2078 | "cfg-if", 2079 | "once_cell", 2080 | "rustversion", 2081 | "wasm-bindgen-macro", 2082 | ] 2083 | 2084 | [[package]] 2085 | name = "wasm-bindgen-backend" 2086 | version = "0.2.100" 2087 | source = "registry+https://github.com/rust-lang/crates.io-index" 2088 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2089 | dependencies = [ 2090 | "bumpalo", 2091 | "log", 2092 | "proc-macro2", 2093 | "quote", 2094 | "syn", 2095 | "wasm-bindgen-shared", 2096 | ] 2097 | 2098 | [[package]] 2099 | name = "wasm-bindgen-macro" 2100 | version = "0.2.100" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2103 | dependencies = [ 2104 | "quote", 2105 | "wasm-bindgen-macro-support", 2106 | ] 2107 | 2108 | [[package]] 2109 | name = "wasm-bindgen-macro-support" 2110 | version = "0.2.100" 2111 | source = "registry+https://github.com/rust-lang/crates.io-index" 2112 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2113 | dependencies = [ 2114 | "proc-macro2", 2115 | "quote", 2116 | "syn", 2117 | "wasm-bindgen-backend", 2118 | "wasm-bindgen-shared", 2119 | ] 2120 | 2121 | [[package]] 2122 | name = "wasm-bindgen-shared" 2123 | version = "0.2.100" 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" 2125 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2126 | dependencies = [ 2127 | "unicode-ident", 2128 | ] 2129 | 2130 | [[package]] 2131 | name = "web-time" 2132 | version = "1.1.0" 2133 | source = "registry+https://github.com/rust-lang/crates.io-index" 2134 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 2135 | dependencies = [ 2136 | "js-sys", 2137 | "wasm-bindgen", 2138 | ] 2139 | 2140 | [[package]] 2141 | name = "webpki-root-certs" 2142 | version = "0.26.11" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" 2145 | dependencies = [ 2146 | "webpki-root-certs 1.0.0", 2147 | ] 2148 | 2149 | [[package]] 2150 | name = "webpki-root-certs" 2151 | version = "1.0.0" 2152 | source = "registry+https://github.com/rust-lang/crates.io-index" 2153 | checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" 2154 | dependencies = [ 2155 | "rustls-pki-types", 2156 | ] 2157 | 2158 | [[package]] 2159 | name = "webpki-roots" 2160 | version = "0.25.4" 2161 | source = "registry+https://github.com/rust-lang/crates.io-index" 2162 | checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 2163 | 2164 | [[package]] 2165 | name = "wide" 2166 | version = "0.7.32" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" 2169 | dependencies = [ 2170 | "bytemuck", 2171 | "safe_arch", 2172 | ] 2173 | 2174 | [[package]] 2175 | name = "widestring" 2176 | version = "1.2.0" 2177 | source = "registry+https://github.com/rust-lang/crates.io-index" 2178 | checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" 2179 | 2180 | [[package]] 2181 | name = "winapi-util" 2182 | version = "0.1.9" 2183 | source = "registry+https://github.com/rust-lang/crates.io-index" 2184 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2185 | dependencies = [ 2186 | "windows-sys 0.59.0", 2187 | ] 2188 | 2189 | [[package]] 2190 | name = "windows-core" 2191 | version = "0.61.2" 2192 | source = "registry+https://github.com/rust-lang/crates.io-index" 2193 | checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 2194 | dependencies = [ 2195 | "windows-implement", 2196 | "windows-interface", 2197 | "windows-link", 2198 | "windows-result", 2199 | "windows-strings", 2200 | ] 2201 | 2202 | [[package]] 2203 | name = "windows-implement" 2204 | version = "0.60.0" 2205 | source = "registry+https://github.com/rust-lang/crates.io-index" 2206 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 2207 | dependencies = [ 2208 | "proc-macro2", 2209 | "quote", 2210 | "syn", 2211 | ] 2212 | 2213 | [[package]] 2214 | name = "windows-interface" 2215 | version = "0.59.1" 2216 | source = "registry+https://github.com/rust-lang/crates.io-index" 2217 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 2218 | dependencies = [ 2219 | "proc-macro2", 2220 | "quote", 2221 | "syn", 2222 | ] 2223 | 2224 | [[package]] 2225 | name = "windows-link" 2226 | version = "0.1.1" 2227 | source = "registry+https://github.com/rust-lang/crates.io-index" 2228 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 2229 | 2230 | [[package]] 2231 | name = "windows-result" 2232 | version = "0.3.4" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 2235 | dependencies = [ 2236 | "windows-link", 2237 | ] 2238 | 2239 | [[package]] 2240 | name = "windows-strings" 2241 | version = "0.4.2" 2242 | source = "registry+https://github.com/rust-lang/crates.io-index" 2243 | checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 2244 | dependencies = [ 2245 | "windows-link", 2246 | ] 2247 | 2248 | [[package]] 2249 | name = "windows-sys" 2250 | version = "0.45.0" 2251 | source = "registry+https://github.com/rust-lang/crates.io-index" 2252 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 2253 | dependencies = [ 2254 | "windows-targets 0.42.2", 2255 | ] 2256 | 2257 | [[package]] 2258 | name = "windows-sys" 2259 | version = "0.48.0" 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" 2261 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2262 | dependencies = [ 2263 | "windows-targets 0.48.5", 2264 | ] 2265 | 2266 | [[package]] 2267 | name = "windows-sys" 2268 | version = "0.52.0" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2271 | dependencies = [ 2272 | "windows-targets 0.52.6", 2273 | ] 2274 | 2275 | [[package]] 2276 | name = "windows-sys" 2277 | version = "0.59.0" 2278 | source = "registry+https://github.com/rust-lang/crates.io-index" 2279 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2280 | dependencies = [ 2281 | "windows-targets 0.52.6", 2282 | ] 2283 | 2284 | [[package]] 2285 | name = "windows-targets" 2286 | version = "0.42.2" 2287 | source = "registry+https://github.com/rust-lang/crates.io-index" 2288 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 2289 | dependencies = [ 2290 | "windows_aarch64_gnullvm 0.42.2", 2291 | "windows_aarch64_msvc 0.42.2", 2292 | "windows_i686_gnu 0.42.2", 2293 | "windows_i686_msvc 0.42.2", 2294 | "windows_x86_64_gnu 0.42.2", 2295 | "windows_x86_64_gnullvm 0.42.2", 2296 | "windows_x86_64_msvc 0.42.2", 2297 | ] 2298 | 2299 | [[package]] 2300 | name = "windows-targets" 2301 | version = "0.48.5" 2302 | source = "registry+https://github.com/rust-lang/crates.io-index" 2303 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2304 | dependencies = [ 2305 | "windows_aarch64_gnullvm 0.48.5", 2306 | "windows_aarch64_msvc 0.48.5", 2307 | "windows_i686_gnu 0.48.5", 2308 | "windows_i686_msvc 0.48.5", 2309 | "windows_x86_64_gnu 0.48.5", 2310 | "windows_x86_64_gnullvm 0.48.5", 2311 | "windows_x86_64_msvc 0.48.5", 2312 | ] 2313 | 2314 | [[package]] 2315 | name = "windows-targets" 2316 | version = "0.52.6" 2317 | source = "registry+https://github.com/rust-lang/crates.io-index" 2318 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2319 | dependencies = [ 2320 | "windows_aarch64_gnullvm 0.52.6", 2321 | "windows_aarch64_msvc 0.52.6", 2322 | "windows_i686_gnu 0.52.6", 2323 | "windows_i686_gnullvm", 2324 | "windows_i686_msvc 0.52.6", 2325 | "windows_x86_64_gnu 0.52.6", 2326 | "windows_x86_64_gnullvm 0.52.6", 2327 | "windows_x86_64_msvc 0.52.6", 2328 | ] 2329 | 2330 | [[package]] 2331 | name = "windows_aarch64_gnullvm" 2332 | version = "0.42.2" 2333 | source = "registry+https://github.com/rust-lang/crates.io-index" 2334 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 2335 | 2336 | [[package]] 2337 | name = "windows_aarch64_gnullvm" 2338 | version = "0.48.5" 2339 | source = "registry+https://github.com/rust-lang/crates.io-index" 2340 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2341 | 2342 | [[package]] 2343 | name = "windows_aarch64_gnullvm" 2344 | version = "0.52.6" 2345 | source = "registry+https://github.com/rust-lang/crates.io-index" 2346 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2347 | 2348 | [[package]] 2349 | name = "windows_aarch64_msvc" 2350 | version = "0.42.2" 2351 | source = "registry+https://github.com/rust-lang/crates.io-index" 2352 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 2353 | 2354 | [[package]] 2355 | name = "windows_aarch64_msvc" 2356 | version = "0.48.5" 2357 | source = "registry+https://github.com/rust-lang/crates.io-index" 2358 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2359 | 2360 | [[package]] 2361 | name = "windows_aarch64_msvc" 2362 | version = "0.52.6" 2363 | source = "registry+https://github.com/rust-lang/crates.io-index" 2364 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2365 | 2366 | [[package]] 2367 | name = "windows_i686_gnu" 2368 | version = "0.42.2" 2369 | source = "registry+https://github.com/rust-lang/crates.io-index" 2370 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 2371 | 2372 | [[package]] 2373 | name = "windows_i686_gnu" 2374 | version = "0.48.5" 2375 | source = "registry+https://github.com/rust-lang/crates.io-index" 2376 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2377 | 2378 | [[package]] 2379 | name = "windows_i686_gnu" 2380 | version = "0.52.6" 2381 | source = "registry+https://github.com/rust-lang/crates.io-index" 2382 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2383 | 2384 | [[package]] 2385 | name = "windows_i686_gnullvm" 2386 | version = "0.52.6" 2387 | source = "registry+https://github.com/rust-lang/crates.io-index" 2388 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2389 | 2390 | [[package]] 2391 | name = "windows_i686_msvc" 2392 | version = "0.42.2" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 2395 | 2396 | [[package]] 2397 | name = "windows_i686_msvc" 2398 | version = "0.48.5" 2399 | source = "registry+https://github.com/rust-lang/crates.io-index" 2400 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2401 | 2402 | [[package]] 2403 | name = "windows_i686_msvc" 2404 | version = "0.52.6" 2405 | source = "registry+https://github.com/rust-lang/crates.io-index" 2406 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2407 | 2408 | [[package]] 2409 | name = "windows_x86_64_gnu" 2410 | version = "0.42.2" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 2413 | 2414 | [[package]] 2415 | name = "windows_x86_64_gnu" 2416 | version = "0.48.5" 2417 | source = "registry+https://github.com/rust-lang/crates.io-index" 2418 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2419 | 2420 | [[package]] 2421 | name = "windows_x86_64_gnu" 2422 | version = "0.52.6" 2423 | source = "registry+https://github.com/rust-lang/crates.io-index" 2424 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2425 | 2426 | [[package]] 2427 | name = "windows_x86_64_gnullvm" 2428 | version = "0.42.2" 2429 | source = "registry+https://github.com/rust-lang/crates.io-index" 2430 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 2431 | 2432 | [[package]] 2433 | name = "windows_x86_64_gnullvm" 2434 | version = "0.48.5" 2435 | source = "registry+https://github.com/rust-lang/crates.io-index" 2436 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2437 | 2438 | [[package]] 2439 | name = "windows_x86_64_gnullvm" 2440 | version = "0.52.6" 2441 | source = "registry+https://github.com/rust-lang/crates.io-index" 2442 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2443 | 2444 | [[package]] 2445 | name = "windows_x86_64_msvc" 2446 | version = "0.42.2" 2447 | source = "registry+https://github.com/rust-lang/crates.io-index" 2448 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 2449 | 2450 | [[package]] 2451 | name = "windows_x86_64_msvc" 2452 | version = "0.48.5" 2453 | source = "registry+https://github.com/rust-lang/crates.io-index" 2454 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2455 | 2456 | [[package]] 2457 | name = "windows_x86_64_msvc" 2458 | version = "0.52.6" 2459 | source = "registry+https://github.com/rust-lang/crates.io-index" 2460 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2461 | 2462 | [[package]] 2463 | name = "winreg" 2464 | version = "0.50.0" 2465 | source = "registry+https://github.com/rust-lang/crates.io-index" 2466 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2467 | dependencies = [ 2468 | "cfg-if", 2469 | "windows-sys 0.48.0", 2470 | ] 2471 | 2472 | [[package]] 2473 | name = "wit-bindgen-rt" 2474 | version = "0.39.0" 2475 | source = "registry+https://github.com/rust-lang/crates.io-index" 2476 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 2477 | dependencies = [ 2478 | "bitflags", 2479 | ] 2480 | 2481 | [[package]] 2482 | name = "writeable" 2483 | version = "0.6.1" 2484 | source = "registry+https://github.com/rust-lang/crates.io-index" 2485 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 2486 | 2487 | [[package]] 2488 | name = "x509-parser" 2489 | version = "0.17.0" 2490 | source = "registry+https://github.com/rust-lang/crates.io-index" 2491 | checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" 2492 | dependencies = [ 2493 | "asn1-rs", 2494 | "data-encoding", 2495 | "der-parser", 2496 | "lazy_static", 2497 | "nom", 2498 | "oid-registry", 2499 | "rusticata-macros", 2500 | "thiserror 2.0.12", 2501 | "time", 2502 | ] 2503 | 2504 | [[package]] 2505 | name = "yasna" 2506 | version = "0.5.2" 2507 | source = "registry+https://github.com/rust-lang/crates.io-index" 2508 | checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" 2509 | dependencies = [ 2510 | "time", 2511 | ] 2512 | 2513 | [[package]] 2514 | name = "yoke" 2515 | version = "0.8.0" 2516 | source = "registry+https://github.com/rust-lang/crates.io-index" 2517 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 2518 | dependencies = [ 2519 | "serde", 2520 | "stable_deref_trait", 2521 | "yoke-derive", 2522 | "zerofrom", 2523 | ] 2524 | 2525 | [[package]] 2526 | name = "yoke-derive" 2527 | version = "0.8.0" 2528 | source = "registry+https://github.com/rust-lang/crates.io-index" 2529 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 2530 | dependencies = [ 2531 | "proc-macro2", 2532 | "quote", 2533 | "syn", 2534 | "synstructure", 2535 | ] 2536 | 2537 | [[package]] 2538 | name = "zerocopy" 2539 | version = "0.8.25" 2540 | source = "registry+https://github.com/rust-lang/crates.io-index" 2541 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 2542 | dependencies = [ 2543 | "zerocopy-derive", 2544 | ] 2545 | 2546 | [[package]] 2547 | name = "zerocopy-derive" 2548 | version = "0.8.25" 2549 | source = "registry+https://github.com/rust-lang/crates.io-index" 2550 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 2551 | dependencies = [ 2552 | "proc-macro2", 2553 | "quote", 2554 | "syn", 2555 | ] 2556 | 2557 | [[package]] 2558 | name = "zerofrom" 2559 | version = "0.1.6" 2560 | source = "registry+https://github.com/rust-lang/crates.io-index" 2561 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2562 | dependencies = [ 2563 | "zerofrom-derive", 2564 | ] 2565 | 2566 | [[package]] 2567 | name = "zerofrom-derive" 2568 | version = "0.1.6" 2569 | source = "registry+https://github.com/rust-lang/crates.io-index" 2570 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2571 | dependencies = [ 2572 | "proc-macro2", 2573 | "quote", 2574 | "syn", 2575 | "synstructure", 2576 | ] 2577 | 2578 | [[package]] 2579 | name = "zeroize" 2580 | version = "1.8.1" 2581 | source = "registry+https://github.com/rust-lang/crates.io-index" 2582 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2583 | 2584 | [[package]] 2585 | name = "zerotrie" 2586 | version = "0.2.2" 2587 | source = "registry+https://github.com/rust-lang/crates.io-index" 2588 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 2589 | dependencies = [ 2590 | "displaydoc", 2591 | "yoke", 2592 | "zerofrom", 2593 | ] 2594 | 2595 | [[package]] 2596 | name = "zerovec" 2597 | version = "0.11.2" 2598 | source = "registry+https://github.com/rust-lang/crates.io-index" 2599 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 2600 | dependencies = [ 2601 | "yoke", 2602 | "zerofrom", 2603 | "zerovec-derive", 2604 | ] 2605 | 2606 | [[package]] 2607 | name = "zerovec-derive" 2608 | version = "0.11.1" 2609 | source = "registry+https://github.com/rust-lang/crates.io-index" 2610 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 2611 | dependencies = [ 2612 | "proc-macro2", 2613 | "quote", 2614 | "syn", 2615 | ] 2616 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rstun" 3 | version = "0.7.1" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["lib"] 8 | 9 | [dependencies] 10 | rustls = { version = "0.23.27", default-features = false, features = ["ring"] } 11 | clap = { version = "4.5", features = ["derive"] } 12 | rcgen = "0.13" 13 | tokio = { version = "1.45", features = ["full"] } 14 | pretty_env_logger = "0.5.0" 15 | ring = "0.17" 16 | log = "0.4" 17 | chrono = "0.4" 18 | anyhow = "1.0" 19 | quinn = "0.11.8" 20 | quinn-proto = "0.11.12" 21 | futures-util = "0.3" 22 | bincode = { version = "2", features = ["serde"] } 23 | pin-utils = "0.1.0" 24 | enum-as-inner = "0.6" 25 | num_cpus = "1.17" 26 | rs-utilities = "0.4.2" 27 | # rs-utilities = { path = "../rs-utilities" } 28 | serde = { version = "1.0", features = ["derive"] } 29 | serde_json = "1.0" 30 | rustls-platform-verifier = "0.6.0" 31 | byte-pool = { git = "https://github.com/neevek/byte-pool" } 32 | x509-parser = "0.17" 33 | lazy_static = "1.5" 34 | rustls-pemfile = "2.2" 35 | bytes = "1" 36 | backon = "1.5" 37 | dashmap = "6" 38 | ctrlc = "3.4" 39 | 40 | [dev-dependencies] 41 | jni = "0.21" 42 | android_logger = "0.15" 43 | 44 | [target.aarch64-linux-android.dependencies] 45 | jni = "0.21" 46 | android_logger = "0.15" 47 | 48 | [target.armv7-linux-androideabi.dependencies] 49 | jni = "0.21" 50 | android_logger = "0.15" 51 | 52 | [profile.release] 53 | opt-level = "z" 54 | strip = true 55 | lto = "fat" 56 | panic = "abort" 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rstun 2 | 3 | A high-performance TCP/UDP tunnel over QUIC, written in Rust. 4 | 5 | rstun leverages the [Quinn](https://github.com/quinn-rs/quinn) library for [QUIC](https://quicwg.org/) transport, providing efficient, low-latency, and secure bidirectional communication. All traffic is protected by QUIC’s integrated TLS layer. 6 | 7 | --- 8 | 9 | ## Features 10 | 11 | - **Multiple TCP and UDP tunnels**: Support for running multiple tunnels (TCP and/or UDP) simultaneously in a single client or server instance. 12 | - **Bidirectional tunneling**: Both inbound (IN) and outbound (OUT) modes for flexible deployment. 13 | - **Modern encryption**: Security via QUIC’s TLS 1.3 layer, with configurable cipher suites. 14 | - **Automatic or custom certificates**: Use your own certificate/key or let rstun generate a self-signed certificate for testing. 15 | - **Traffic statistics**: Real-time tunnel traffic reporting. 16 | 17 | --- 18 | 19 | ## Operating Modes 20 | 21 | ### Inbound Tunneling (IN Mode) 22 | Expose a local service (e.g., web server) to the public internet securely through the QUIC tunnel. Useful for making services behind NAT/firewall accessible externally. 23 | 24 | ### Outbound Tunneling (OUT Mode) 25 | Tunnel local outbound traffic through the server, which then forwards it to the specified destination. Commonly used to encrypt and route traffic from a local network to external servers. 26 | 27 | --- 28 | 29 | ## Components 30 | 31 | - **rstunc** (client): Establishes and manages tunnels to the server. 32 | - **rstund** (server): Accepts incoming connections and forwards TCP/UDP traffic according to configuration. 33 | 34 | --- 35 | 36 | ## Example Usage 37 | 38 | ### Start the server 39 | 40 | ```sh 41 | rstund \ 42 | --addr 0.0.0.0:6060 \ 43 | --tcp-upstream 8800 \ 44 | --udp-upstream 8.8.8.8:53 \ 45 | --password 123456 \ 46 | --cert path/to/cert.der \ 47 | --key path/to/key.der 48 | ``` 49 | 50 | - `--addr` — IP:port to listen on. 51 | - `--tcp-upstream` — Default TCP upstream for OUT tunnels (if client does not specify one). 52 | - `--udp-upstream` — Default UDP upstream for OUT tunnels (if client does not specify one). 53 | - `--password` — Required for client authentication. 54 | - `--cert`/`--key` — Certificate and private key for the server. If omitted, a self-signed certificate for `localhost` is generated (for testing only). 55 | 56 | ### Start the client (multiple tunnels example) 57 | 58 | ```sh 59 | rstunc \ 60 | --server-addr 1.2.3.4:6060 \ 61 | --password 123456 \ 62 | --cert path/to/cert.der \ 63 | --tcp-mappings "OUT^0.0.0.0:9900^8800,IN^127.0.0.1:8080^9000" \ 64 | --udp-mappings "OUT^0.0.0.0:9900^8.8.8.8:53" \ 65 | --loglevel D 66 | ``` 67 | 68 | - `--tcp-mappings` and `--udp-mappings` now accept **comma-separated lists** of mappings, each in the form `MODE^[ip:]port^[ip:]port` (e.g., `OUT^8000^ANY`). 69 | - `MODE` is either `OUT` or `IN`. 70 | - `ANY` as the destination means the server’s default upstream is used. 71 | 72 | #### Simple test 73 | 74 | ```sh 75 | # Start server with auto-generated self-signed certificate 76 | rstund -a 9000 -p 1234 77 | 78 | # Start client with both TCP and UDP tunnels 79 | rstunc \ 80 | --server-addr 127.0.0.1:9000 \ 81 | --password 1234 \ 82 | --tcp-mappings "OUT^0.0.0.0:9900^8800" \ 83 | --udp-mappings "OUT^0.0.0.0:9900^8800" 84 | ``` 85 | 86 | --- 87 | 88 | ## Command-Line Options 89 | 90 | ### rstund (server) 91 | 92 | ``` 93 | Usage: rstund [OPTIONS] --addr --password 94 | 95 | Options: 96 | -a, --addr Address ([ip:]port) to listen on 97 | -t, --tcp-upstream Default TCP upstream for OUT tunnels ([ip:]port) 98 | -u, --udp-upstream Default UDP upstream for OUT tunnels ([ip:]port) 99 | -p, --password Server password (required) 100 | -c, --cert Path to certificate file (optional) 101 | -k, --key Path to key file (optional) 102 | -w, --workers Number of async worker threads [default: 0] 103 | --quic-timeout-ms QUIC idle timeout (ms) [default: 40000] 104 | --tcp-timeout-ms TCP idle timeout (ms) [default: 30000] 105 | --udp-timeout-ms UDP idle timeout (ms) [default: 30000] 106 | -l, --loglevel Log level [default: I] [T, D, I, W, E] 107 | -h, --help Print help 108 | -V, --version Print version 109 | ``` 110 | 111 | ### rstunc (client) 112 | 113 | ``` 114 | Usage: rstunc [OPTIONS] --server-addr --password 115 | 116 | Options: 117 | -a, --server-addr Server address ([:port]) 118 | -p, --password Password for server authentication 119 | -t, --tcp-mappings Comma-separated list of TCP tunnel mappings (MODE^[ip:]port^[ip:]port) 120 | -u, --udp-mappings Comma-separated list of UDP tunnel mappings (MODE^[ip:]port^[ip:]port) 121 | -c, --cert Path to certificate file (optional) 122 | -e, --cipher Cipher suite [default: chacha20-poly1305] [chacha20-poly1305, aes-256-gcm, aes-128-gcm] 123 | -w, --workers Number of async worker threads [default: 0] 124 | -r, --wait-before-retry-ms Wait before retry (ms) [default: 5000] 125 | --quic-timeout-ms QUIC idle timeout (ms) [default: 30000] 126 | --tcp-timeout-ms TCP idle timeout (ms) [default: 30000] 127 | --udp-timeout-ms UDP idle timeout (ms) [default: 5000] 128 | --dot Comma-separated DoT servers for DNS resolution 129 | --dns Comma-separated DNS servers for resolution 130 | -l, --loglevel Log level [default: I] [T, D, I, W, E] 131 | -h, --help Print help 132 | -V, --version Print version 133 | ``` 134 | 135 | --- 136 | 137 | ## Notes 138 | 139 | - **Multiple tunnels**: You can specify multiple TCP and/or UDP tunnels in a single client or server instance using the new `--tcp-mappings` and `--udp-mappings` options. 140 | - **Mapping format**: Each mapping is `MODE^[ip:]port^[ip:]port`, where `MODE` is `OUT` or `IN`. 141 | - **Self-signed certificates**: If no certificate is provided, a self-signed certificate for `localhost` is generated (for testing only). 142 | - **Security**: For production, always use a valid certificate and connect via domain name. 143 | 144 | --- 145 | 146 | ## License 147 | 148 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, you can obtain one at [http://mozilla.org/MPL/2.0/](http://mozilla.org/MPL/2.0/). 149 | -------------------------------------------------------------------------------- /gen_cert_and_key.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domain_or_ip=$1 4 | if [[ -z $domain_or_ip ]]; then 5 | echo "usage: $0 " 6 | exit 1 7 | fi 8 | 9 | openssl_cnf= 10 | platform=$(uname 2> /dev/null) 11 | 12 | if [[ "$platform" = "Darwin" ]]; then 13 | openssl_cnf="/System/Library/OpenSSL/openssl.cnf" 14 | elif [[ "$platform" = "Linux" ]]; then 15 | openssl_cnf="/etc/ssl/openssl.cnf" 16 | else 17 | echo "not supported!" 18 | exit 1 19 | fi 20 | 21 | if [[ $domain_or_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ || $domain_or_ip =~ : ]]; then 22 | san_type="IP" 23 | else 24 | san_type="DNS" 25 | fi 26 | 27 | openssl req \ 28 | -newkey rsa:2048 \ 29 | -x509 \ 30 | -nodes \ 31 | -keyout $domain_or_ip.key.pem \ 32 | -new \ 33 | -out $domain_or_ip.crt.pem \ 34 | -subj /CN=$domain_or_ip \ 35 | -reqexts san \ 36 | -extensions san \ 37 | -config <(cat $openssl_cnf \ 38 | <(printf "[san]\nsubjectAltName=$san_type:$domain_or_ip")) \ 39 | -sha256 \ 40 | -days 3650 41 | -------------------------------------------------------------------------------- /localhost.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC7TCCAdWgAwIBAgIUYPwMIryUb2HDSMECaNPOvjJa4xkwDQYJKoZIhvcNAQEL 3 | BQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MB4XDTIzMTAxMzEzNTg1M1oXDTMzMTAx 4 | MDEzNTg1M1owFDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 5 | AAOCAQ8AMIIBCgKCAQEAo62orpJ8jDcl7j3Xa4PbZw0cW9wjEj3w0q2uG+jdUqdl 6 | 2qrAyaHp8hq00N0yDLgiNhcGEv5kw1TQ1H4Z3v9cz4kHMWnc5BFH4dtCkK5TZrWg 7 | k5eHrRkebtFtzTIEle7ObVbnwjrcPm06+Ys7kwoZSJKoAvgmprsJGL0OI7S1sxUw 8 | zMvhDdxfdR4XkTR1pcAeSYzfpkFvXU6tItWnSFuNCKVx1LlMl1BaZ+t8KB2yRSn4 9 | YRfT5sQPhbVyzNEoEgpdQRaE9sAKKb1/+ezXMKT9BrE1uMUIJtbNE/wd41hgFxUi 10 | 97AcXizKTf/Ey84pGae8Juzils0QaZabRkktjZ0QfQIDAQABozcwNTAUBgNVHREE 11 | DTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFFVgOh7RgV4czbC53EgO3Iu1J1H7MA0G 12 | CSqGSIb3DQEBCwUAA4IBAQAmWYntJkwVG4XKjbdMMdCfTjw/7nvvfDrRqpZhZKpk 13 | NXh/UrNVQGgC2zoivfaVSb9cJi+CZ0M9X8WActAXtqo9j0PqX2y6XLIPAbulHWbv 14 | +KDQHuqYnUMz7YouvgCQDqqdVTW9IREdQgtHsFWajPL5JHd8rqS7KPINwRboSxO5 15 | BYTEq17egIigR3Yr+Fr20mrz5/w3JC4WLTZnr5bQ06HaXYqWuSOwGBQvfS5w3Y/t 16 | XlmSqLz9Q8YbO8bbpybErBUd/dv0qo8BXYUhSWZyFzgqqqp0ciIPzPs1y5OCbIz1 17 | yq8KmcY2W6ILipKklCqs2V0esasj9zwVQ2W95oBWv0Y7 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /localhost.key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjraiuknyMNyXu 3 | Pddrg9tnDRxb3CMSPfDSra4b6N1Sp2XaqsDJoenyGrTQ3TIMuCI2FwYS/mTDVNDU 4 | fhne/1zPiQcxadzkEUfh20KQrlNmtaCTl4etGR5u0W3NMgSV7s5tVufCOtw+bTr5 5 | izuTChlIkqgC+CamuwkYvQ4jtLWzFTDMy+EN3F91HheRNHWlwB5JjN+mQW9dTq0i 6 | 1adIW40IpXHUuUyXUFpn63woHbJFKfhhF9PmxA+FtXLM0SgSCl1BFoT2wAopvX/5 7 | 7NcwpP0GsTW4xQgm1s0T/B3jWGAXFSL3sBxeLMpN/8TLzikZp7wm7OKWzRBplptG 8 | SS2NnRB9AgMBAAECggEAA2AX6+2yatPSUOzujdWMxz3CeXR7NquXVUjmn8W4FrXE 9 | CHPgAOh5YhhB5VLCbve3IOVtpyOe4VZU7iThlLMwb0k0oES+HOfsUxCJ+WDW8HXL 10 | Z2/HCP9NHNztxj8DUDI6CJAzvFIpa5ImFrJT2q7pIZGArHsAlQyjXeK8MWlPG+/J 11 | +u13xNDs0pnnXa3ks6l5Wv107z34b7aa6TLNz0BsfXnMvxAkgrgAfWm4kgJCfgF/ 12 | P8xgrE9CZg/QyMTGFnyVw38ofYgAdMCpQNyz2GeF+jm3HEx15KCfsNogVKXNjXTR 13 | PkWIi8CUkRldjpztMCZuxFgBYQ4uqEDte6LNGOa9MQKBgQDCDwPw9JZsVeJjmc2I 14 | wndNFsbRzHGDtlwJ2I01ofQi1RQshG2OwygLb2b1iJmaJgfL7OrfSh8oM4j4cFGW 15 | /pWfrkwOfgK8GoguydOCBAC+xSvTyM/4OpdTJnTxx4KYxK/JYAzJfqqHTysTXD0z 16 | Scnuqg/O+k4c6yKGzprOh/YgyQKBgQDX7DLT6otws7HoIrrcTZwohwGLxuNlmnAz 17 | AHapI6kY9arM3nnZ45e9/IzdDKdTdEeJHlg+Qq7PA7awPDD8JKkRR/wwEQiyNsZQ 18 | /nq/5E7upxuffvSNZXkYGPCMqR3QjeXtgwiaUyAVBv0awcb7euOYfiNPE9F/ZGRo 19 | dn2lcplgFQKBgB4ConJl8MlKMtuCUoW+xSJXzmFtg3SCBBPFuHi91fp5B0inJiY4 20 | yf2SudJo0JBFJ1mDBwOG+/CEn78D56o5LrxmAP8Qv62FUOAjeCEYK2FVLqTu3jKe 21 | JP7H6LfnSawEZsb2oqOaghawyJGS5ygCVkchZ5ZzoRbZyhoc41XjMs35AoGBAJ89 22 | vjqVtqK2k9Vcj6zbu/gToStB0gDjxi2HAPw6pYIX5BBVX043UHi4IfcAVwLwNbXF 23 | YFUCfsODvJ76tTGvo9Rv32hfl6c/SEEBfOu6aBAPxAp76cXB+W2xLu695pQholnp 24 | ElYmSfnX/qBWGvbqqaGUHVw7hHzMQFTeVknHW6AFAoGALqfMktqZCIg51s+ZUSA3 25 | lJ2b9h5x/Xf2KztaGZxAa99uRZhVbK7f9yGVgAFABFXLtN71UdfGy0vf3nIZY+s4 26 | 7Rkutd1YJA0y94D4Yi7i44duAOuPdlQJur9pX80VPJZOeDKYVqkuceoGDYk5RS7M 27 | W0BfQBQKqgmZdy5vuVYw97U= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/bin/rstunc.rs: -------------------------------------------------------------------------------- 1 | use clap::builder::PossibleValuesParser; 2 | use clap::builder::TypedValueParser as _; 3 | use clap::Parser; 4 | use log::error; 5 | use rstun::*; 6 | 7 | fn main() { 8 | let args = RstuncArgs::parse(); 9 | let log_filter = format!("rstun={},rs_utilities={}", args.loglevel, args.loglevel); 10 | rs_utilities::LogHelper::init_logger("rstunc", log_filter.as_str()); 11 | 12 | let config = ClientConfig::create( 13 | &args.server_addr, 14 | &args.password, 15 | &args.cert, 16 | &args.cipher, 17 | &args.tcp_mappings, 18 | &args.udp_mappings, 19 | &args.dot, 20 | &args.dns, 21 | args.workers, 22 | args.wait_before_retry_ms, 23 | args.quic_timeout_ms, 24 | args.tcp_timeout_ms, 25 | args.udp_timeout_ms, 26 | ) 27 | .map_err(|e| { 28 | error!("{e}"); 29 | }); 30 | 31 | if let Ok(config) = config { 32 | let mut client = Client::new(config); 33 | 34 | #[cfg(target_os = "android")] 35 | { 36 | use log::info; 37 | client.set_enable_on_info_report(true); 38 | client.set_on_info_listener(|s| { 39 | info!("{}", s); 40 | }); 41 | } 42 | 43 | client.start_tunneling(); 44 | } 45 | } 46 | 47 | #[derive(Parser, Debug)] 48 | #[command(author, version, about, long_about = None)] 49 | struct RstuncArgs { 50 | /// Server address ([:port]) of rstund. Default port is 3515. 51 | #[arg(short = 'a', long)] 52 | server_addr: String, 53 | 54 | /// Password for server authentication (must match server's --password) 55 | #[arg(short = 'p', long, required = true)] 56 | password: String, 57 | 58 | /// Comma-separated list of TCP tunnel mappings. Each mapping is in the form MODE^[ip:]port^[ip:]port, e.g. OUT^8080^0.0.0.0:9090 59 | /// MODE is either OUT or IN. Use OUT^8000^ANY to use the server's default upstream for OUT mode. 60 | #[arg(short = 't', long, verbatim_doc_comment, default_value = "")] 61 | tcp_mappings: String, 62 | 63 | /// Comma-separated list of UDP tunnel mappings. Each mapping is in the form MODE^[ip:]port^[ip:]port, e.g. OUT^8080^0.0.0.0:9090 64 | /// MODE is either OUT or IN. Use OUT^8000^ANY to use the server's default upstream for OUT mode. 65 | #[arg(short = 'u', long, verbatim_doc_comment, default_value = "")] 66 | udp_mappings: String, 67 | 68 | /// Path to the certificate file (only needed for self-signed certificates) 69 | #[arg(short = 'c', long, default_value = "")] 70 | cert: String, 71 | 72 | /// Preferred cipher suite 73 | #[arg(short = 'e', long, default_value_t = String::from(SUPPORTED_CIPHER_SUITE_STRS[0]), 74 | value_parser = PossibleValuesParser::new(SUPPORTED_CIPHER_SUITE_STRS).map(|v| v.to_string()))] 75 | cipher: String, 76 | 77 | /// Number of async worker threads [uses all logical CPUs if 0] 78 | #[arg(short = 'w', long, default_value_t = 0)] 79 | workers: usize, 80 | 81 | /// Wait time in milliseconds before retrying connection 82 | #[arg(short = 'r', long, default_value_t = 5000)] 83 | wait_before_retry_ms: u64, 84 | 85 | /// QUIC idle timeout in milliseconds 86 | #[arg(long, default_value_t = 30000)] 87 | quic_timeout_ms: u64, 88 | 89 | /// TCP idle timeout in milliseconds 90 | #[arg(long, default_value_t = 30000)] 91 | tcp_timeout_ms: u64, 92 | 93 | /// UDP idle timeout in milliseconds 94 | #[arg(long, default_value_t = 5000)] 95 | udp_timeout_ms: u64, 96 | 97 | /// Comma-separated DoT servers (domains) for DNS resolution, e.g. "dns.google,one.one.one.one". Takes precedence over --dns if set. 98 | #[arg(long, verbatim_doc_comment, default_value = "")] 99 | dot: String, 100 | 101 | /// Comma-separated DNS servers (IPs) for DNS resolution, e.g. "1.1.1.1,8.8.8.8" 102 | #[arg(long, verbatim_doc_comment, default_value = "")] 103 | dns: String, 104 | 105 | /// Log level 106 | #[arg(short = 'l', long, default_value_t = String::from("I"), 107 | value_parser = PossibleValuesParser::new(["T", "D", "I", "W", "E"]).map(|v| match v.as_str() { 108 | "T" => "trace", 109 | "D" => "debug", 110 | "I" => "info", 111 | "W" => "warn", 112 | "E" => "error", 113 | _ => "info", 114 | }.to_string()))] 115 | loglevel: String, 116 | } 117 | -------------------------------------------------------------------------------- /src/bin/rstund.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use clap::builder::PossibleValuesParser; 3 | use clap::builder::TypedValueParser as _; 4 | use clap::Parser; 5 | use log::error; 6 | use log::info; 7 | use rs_utilities::log_and_bail; 8 | use rstun::*; 9 | use std::net::SocketAddr; 10 | 11 | fn main() { 12 | let args = RstundArgs::parse(); 13 | let log_filter = format!("rstun={},rs_utilities={}", args.loglevel, args.loglevel); 14 | rs_utilities::LogHelper::init_logger("rstund", log_filter.as_str()); 15 | 16 | let workers = if args.workers > 0 { 17 | args.workers 18 | } else { 19 | num_cpus::get() 20 | }; 21 | 22 | info!("will use {} workers", workers); 23 | 24 | tokio::runtime::Builder::new_multi_thread() 25 | .enable_all() 26 | .worker_threads(workers) 27 | .build() 28 | .unwrap() 29 | .block_on(async { 30 | run(args) 31 | .await 32 | .map_err(|e| { 33 | error!("{e}"); 34 | }) 35 | .ok(); 36 | }) 37 | } 38 | 39 | async fn run(mut args: RstundArgs) -> Result<()> { 40 | if args.addr.is_empty() { 41 | args.addr = "0.0.0.0:0".to_string(); 42 | } 43 | 44 | if !args.addr.contains(':') { 45 | args.addr = format!("127.0.0.1:{}", args.addr); 46 | } 47 | 48 | let config = ServerConfig { 49 | addr: args.addr, 50 | password: args.password, 51 | cert_path: args.cert, 52 | key_path: args.key, 53 | default_tcp_upstream: parse_upstreams("tcp", &args.tcp_upstream)?, 54 | default_udp_upstream: parse_upstreams("udp", &args.udp_upstream)?, 55 | quic_timeout_ms: args.quic_timeout_ms, 56 | tcp_timeout_ms: args.tcp_timeout_ms, 57 | udp_timeout_ms: args.udp_timeout_ms, 58 | dashboard_server: "".to_string(), 59 | dashboard_server_credential: "".to_string(), 60 | }; 61 | 62 | let mut server = Server::new(config); 63 | server.bind()?; 64 | server.serve().await?; 65 | Ok(()) 66 | } 67 | 68 | fn parse_upstreams(upstream_type: &str, upstreams_str: &str) -> Result> { 69 | if upstreams_str.is_empty() { 70 | return Ok(None); 71 | } 72 | 73 | let mut upstream = upstreams_str.to_string(); 74 | if upstream.starts_with("0.0.0.0:") { 75 | upstream = upstream.replace("0.0.0.0:", "127.0.0.1:"); 76 | } 77 | 78 | if !upstream.contains(':') { 79 | upstream = format!("127.0.0.1:{upstreams_str}"); 80 | } 81 | 82 | if let Ok(addr) = upstream.parse() { 83 | Ok(Some(addr)) 84 | } else { 85 | log_and_bail!("invalid {upstream_type} upstream address: {upstreams_str}"); 86 | } 87 | } 88 | 89 | #[derive(Parser, Debug)] 90 | #[command(author, version, about, long_about = None)] 91 | struct RstundArgs { 92 | /// Address ([ip:]port) to listen on. If only a port is given, binds to 127.0.0.1:PORT. 93 | #[arg( 94 | short = 'a', 95 | long, 96 | required = true, 97 | default_value = "", 98 | verbatim_doc_comment 99 | )] 100 | addr: String, 101 | 102 | /// Default TCP upstream for OUT tunnels ([ip:]port). Used if client does not specify an upstream. 103 | #[arg( 104 | short = 't', 105 | long, 106 | required = false, 107 | default_value = "", 108 | verbatim_doc_comment 109 | )] 110 | tcp_upstream: String, 111 | 112 | /// Default UDP upstream for OUT tunnels ([ip:]port). Used if client does not specify an upstream. 113 | #[arg( 114 | short = 'u', 115 | long, 116 | required = false, 117 | default_value = "", 118 | verbatim_doc_comment 119 | )] 120 | udp_upstream: String, 121 | 122 | /// Server password (required, must match client --password) 123 | #[arg(short = 'p', long, required = true)] 124 | password: String, 125 | 126 | /// Path to certificate file (optional). If empty, a self-signed certificate for "localhost" is generated (testing only). 127 | #[arg(short = 'c', long, default_value = "", verbatim_doc_comment)] 128 | cert: String, 129 | 130 | /// Path to key file (optional, only needed if --cert is set) 131 | #[arg(short = 'k', long, default_value = "")] 132 | key: String, 133 | 134 | /// Number of async worker threads [uses all logical CPUs if 0] 135 | #[arg(short = 'w', long, default_value_t = 0)] 136 | workers: usize, 137 | 138 | /// QUIC idle timeout in milliseconds 139 | #[arg(long, default_value_t = 40000)] 140 | quic_timeout_ms: u64, 141 | 142 | /// TCP idle timeout in milliseconds 143 | #[arg(long, default_value_t = 30000)] 144 | tcp_timeout_ms: u64, 145 | 146 | /// UDP idle timeout in milliseconds 147 | #[arg(long, default_value_t = 5000)] 148 | udp_timeout_ms: u64, 149 | 150 | /// Log level 151 | #[arg(short = 'l', long, default_value_t = String::from("I"), 152 | value_parser = PossibleValuesParser::new(["T", "D", "I", "W", "E"]).map(|v| match v.as_str() { 153 | "T" => "trace", 154 | "D" => "debug", 155 | "I" => "info", 156 | "W" => "warn", 157 | "E" => "error", 158 | _ => "info", 159 | }.to_string()))] 160 | loglevel: String, 161 | } 162 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | pem_util, socket_addr_with_unspecified_ip_port, 3 | tcp::tcp_tunnel::TcpTunnel, 4 | tunnel_info_bridge::{TunnelInfo, TunnelInfoBridge, TunnelInfoType, TunnelTraffic}, 5 | tunnel_message::TunnelMessage, 6 | udp::{udp_server::UdpServer, udp_tunnel::UdpTunnel}, 7 | ClientConfig, LoginInfo, SelectedCipherSuite, TcpServer, TunnelConfig, TunnelMode, 8 | UpstreamType, 9 | }; 10 | use anyhow::{bail, Context, Result}; 11 | use backon::ExponentialBuilder; 12 | use backon::Retryable; 13 | use log::{error, info, warn}; 14 | use quinn::{congestion, crypto::rustls::QuicClientConfig, Connection, Endpoint, TransportConfig}; 15 | use quinn_proto::{IdleTimeout, VarInt}; 16 | use rs_utilities::dns::{self, DNSQueryOrdering, DNSResolverConfig, DNSResolverLookupIpStrategy}; 17 | use rs_utilities::log_and_bail; 18 | use rustls::{ 19 | client::danger::ServerCertVerified, 20 | crypto::{ring::cipher_suite, CryptoProvider}, 21 | RootCertStore, SupportedCipherSuite, 22 | }; 23 | use rustls_platform_verifier::{self, BuilderVerifierExt}; 24 | use serde::Serialize; 25 | use std::collections::HashMap; 26 | use std::{ 27 | fmt::Display, 28 | net::{IpAddr, SocketAddr}, 29 | str::FromStr, 30 | sync::{Arc, Mutex, Once}, 31 | time::Duration, 32 | }; 33 | use tokio::net::TcpStream; 34 | 35 | const TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%3f"; 36 | const DEFAULT_SERVER_PORT: u16 = 3515; 37 | const POST_TRAFFIC_DATA_INTERVAL_SECS: u64 = 30; 38 | static INIT: Once = Once::new(); 39 | 40 | #[derive(Clone, Serialize, PartialEq)] 41 | pub enum ClientState { 42 | Idle = 0, 43 | Connecting, 44 | Connected, 45 | LoggingIn, 46 | Tunneling, 47 | Stopping, 48 | Terminated, 49 | } 50 | 51 | impl Display for ClientState { 52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 | match self { 54 | ClientState::Idle => write!(f, "Idle"), 55 | ClientState::Connecting => write!(f, "Connecting"), 56 | ClientState::Connected => write!(f, "Connected"), 57 | ClientState::LoggingIn => write!(f, "LoggingIn"), 58 | ClientState::Tunneling => write!(f, "Tunneling"), 59 | ClientState::Stopping => write!(f, "Stopping"), 60 | ClientState::Terminated => write!(f, "Terminated"), 61 | } 62 | } 63 | } 64 | 65 | struct State { 66 | tcp_servers: HashMap, 67 | udp_servers: HashMap, 68 | connections: HashMap, 69 | client_state: ClientState, 70 | total_traffic_data: TunnelTraffic, 71 | tunnel_info_bridge: TunnelInfoBridge, 72 | on_info_report_enabled: bool, 73 | } 74 | 75 | impl State { 76 | fn new() -> Self { 77 | Self { 78 | tcp_servers: HashMap::new(), 79 | udp_servers: HashMap::new(), 80 | connections: HashMap::new(), 81 | client_state: ClientState::Idle, 82 | total_traffic_data: TunnelTraffic::default(), 83 | tunnel_info_bridge: TunnelInfoBridge::new(), 84 | on_info_report_enabled: false, 85 | } 86 | } 87 | 88 | fn post_tunnel_info(&self, server_info: TunnelInfo) 89 | where 90 | T: ?Sized + Serialize, 91 | { 92 | if self.on_info_report_enabled { 93 | self.tunnel_info_bridge.post_tunnel_info(server_info); 94 | } 95 | } 96 | } 97 | 98 | struct LoginConfig { 99 | local_addr: SocketAddr, 100 | remote_addr: SocketAddr, 101 | quinn_client_cfg: quinn::ClientConfig, 102 | domain: String, 103 | } 104 | 105 | #[derive(Clone)] 106 | pub struct Client { 107 | config: ClientConfig, 108 | inner_state: Arc>, 109 | } 110 | 111 | macro_rules! inner_state { 112 | ($self:ident, $field:ident) => { 113 | (*$self.inner_state.lock().unwrap()).$field 114 | }; 115 | } 116 | 117 | impl Client { 118 | pub fn new(config: ClientConfig) -> Self { 119 | INIT.call_once(|| { 120 | rustls::crypto::ring::default_provider() 121 | .install_default() 122 | .unwrap(); 123 | }); 124 | 125 | Client { 126 | config, 127 | inner_state: Arc::new(Mutex::new(State::new())), 128 | } 129 | } 130 | 131 | pub fn start_tunneling(&mut self) { 132 | let (tx, rx) = std::sync::mpsc::channel(); 133 | ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) 134 | .expect("Error setting Ctrl-C handler"); 135 | 136 | tokio::runtime::Builder::new_multi_thread() 137 | .enable_all() 138 | .worker_threads(self.config.workers) 139 | .build() 140 | .unwrap() 141 | .block_on(async { 142 | self.connect_and_serve_async(); 143 | rx.recv().expect("Could not receive from channel."); 144 | self.stop_async().await; 145 | }); 146 | } 147 | 148 | pub async fn start_tcp_server(&self, addr: SocketAddr) -> Result { 149 | let bind_tcp_server = || async { TcpServer::bind_and_start(addr).await }; 150 | let tcp_server = bind_tcp_server 151 | .retry( 152 | ExponentialBuilder::default() 153 | .with_max_delay(Duration::from_secs(10)) 154 | .with_max_times(10), 155 | ) 156 | .sleep(tokio::time::sleep) 157 | .notify(|err: &anyhow::Error, dur: Duration| { 158 | warn!("will start tcp server ({addr}) after {dur:?}, err: {err:?}"); 159 | }) 160 | .await?; 161 | 162 | inner_state!(self, tcp_servers).insert(addr, tcp_server.clone()); 163 | 164 | Ok(tcp_server) 165 | } 166 | 167 | pub async fn start_udp_server(&self, addr: SocketAddr) -> Result { 168 | // create a local udp server for 'OUT' tunnel 169 | let bind_udp_server = || async { UdpServer::bind_and_start(addr).await }; 170 | let udp_server = bind_udp_server 171 | .retry( 172 | ExponentialBuilder::default() 173 | .with_max_delay(Duration::from_secs(10)) 174 | .with_max_times(10), 175 | ) 176 | .sleep(tokio::time::sleep) 177 | .notify(|err: &anyhow::Error, dur: Duration| { 178 | warn!("will start udp server ({addr}) after {dur:?}, err: {err:?}"); 179 | }) 180 | .await?; 181 | 182 | inner_state!(self, udp_servers).insert(addr, udp_server.clone()); 183 | Ok(udp_server) 184 | } 185 | 186 | pub fn get_config(&self) -> ClientConfig { 187 | self.config.clone() 188 | } 189 | 190 | #[allow(clippy::unnecessary_to_owned)] 191 | pub fn stop(&self) { 192 | self.set_and_post_tunnel_state(ClientState::Stopping); 193 | 194 | if let Ok(mut state) = self.inner_state.lock() { 195 | for mut s in state.tcp_servers.values().cloned() { 196 | tokio::spawn(async move { 197 | s.shutdown().await.ok(); 198 | }); 199 | } 200 | for mut s in state.udp_servers.values().cloned() { 201 | tokio::spawn(async move { 202 | s.shutdown().await.ok(); 203 | }); 204 | } 205 | 206 | for c in state.connections.values().cloned() { 207 | tokio::spawn(async move { 208 | c.close(VarInt::from_u32(1), b""); 209 | }); 210 | } 211 | 212 | state.tcp_servers.clear(); 213 | state.udp_servers.clear(); 214 | state.connections.clear(); 215 | } 216 | 217 | std::thread::sleep(Duration::from_secs(3)); 218 | } 219 | 220 | #[allow(clippy::unnecessary_to_owned)] 221 | pub async fn stop_async(&self) { 222 | self.set_and_post_tunnel_state(ClientState::Stopping); 223 | 224 | let mut tasks = tokio::task::JoinSet::new(); 225 | if let Ok(mut state) = self.inner_state.lock() { 226 | for mut s in state.tcp_servers.values().cloned() { 227 | tasks.spawn(async move { 228 | s.shutdown().await.ok(); 229 | }); 230 | } 231 | for mut s in state.udp_servers.values().cloned() { 232 | tasks.spawn(async move { 233 | s.shutdown().await.ok(); 234 | }); 235 | } 236 | 237 | for c in state.connections.values().cloned() { 238 | tasks.spawn(async move { 239 | c.close(VarInt::from_u32(1), b""); 240 | }); 241 | } 242 | 243 | state.tcp_servers.clear(); 244 | state.udp_servers.clear(); 245 | state.connections.clear(); 246 | } 247 | 248 | while tasks.join_next().await.is_some() {} 249 | } 250 | 251 | #[allow(clippy::unnecessary_to_owned)] 252 | pub fn connect_and_serve_async(&mut self) { 253 | for (index, tunnel_config) in self.config.tunnels.iter().cloned().enumerate() { 254 | let mut this = self.clone(); 255 | tokio::spawn(async move { 256 | this.connect_and_serve(index, tunnel_config.clone()).await; 257 | }); 258 | } 259 | 260 | self.report_traffic_data_in_background(); 261 | } 262 | 263 | async fn connect_and_serve(&mut self, index: usize, tunnel_config: TunnelConfig) { 264 | let login_info = LoginInfo { 265 | password: self.config.password.clone(), 266 | tunnel_config: tunnel_config.clone(), 267 | }; 268 | 269 | let mut pending_tcp_stream = None; 270 | loop { 271 | let connect = || async { 272 | let login_cfg = self.prepare_login_config().await?; 273 | let mut endpoint = quinn::Endpoint::client(login_cfg.local_addr)?; 274 | endpoint.set_default_client_config(login_cfg.quinn_client_cfg); 275 | 276 | let conn = self 277 | .login( 278 | index, 279 | &endpoint, 280 | &login_info, 281 | &login_cfg.remote_addr, 282 | login_cfg.domain.as_str(), 283 | ) 284 | .await?; 285 | 286 | Ok(conn) 287 | }; 288 | let result = connect 289 | .retry( 290 | ExponentialBuilder::default() 291 | .with_max_delay(Duration::from_secs(10)) 292 | .with_max_times(usize::MAX), 293 | ) 294 | .when(|_| !self.should_quit()) 295 | .sleep(tokio::time::sleep) 296 | .notify(|err: &anyhow::Error, dur: Duration| { 297 | warn!("will retry after {dur:?}, err: {err:?}"); 298 | }) 299 | .await; 300 | 301 | if self.should_quit() { 302 | break; 303 | } 304 | 305 | match result { 306 | Ok(conn) => { 307 | let upstream_type = &tunnel_config.upstream.upstream_type; 308 | let local_server_addr = tunnel_config.local_server_addr.unwrap(); 309 | 310 | inner_state!(self, connections).insert(local_server_addr, conn.clone()); 311 | 312 | if tunnel_config.mode == TunnelMode::Out { 313 | match upstream_type { 314 | UpstreamType::Tcp => { 315 | self.serve_outbound_tcp( 316 | index, 317 | conn.clone(), 318 | local_server_addr, 319 | &mut pending_tcp_stream, 320 | ) 321 | .await 322 | .ok(); 323 | } 324 | UpstreamType::Udp => { 325 | self.serve_outbound_udp(index, conn.clone(), local_server_addr) 326 | .await 327 | .ok(); 328 | } 329 | } 330 | } else { 331 | match upstream_type { 332 | UpstreamType::Tcp => { 333 | self.serve_inbound_tcp(index, conn.clone(), local_server_addr) 334 | .await 335 | .ok(); 336 | } 337 | UpstreamType::Udp => { 338 | self.serve_inbound_udp(index, conn.clone(), local_server_addr) 339 | .await 340 | .ok(); 341 | } 342 | } 343 | } 344 | 345 | inner_state!(self, connections).remove(&local_server_addr); 346 | 347 | let stats = conn.stats(); 348 | let data = &mut inner_state!(self, total_traffic_data); 349 | data.rx_bytes += stats.udp_rx.bytes; 350 | data.tx_bytes += stats.udp_tx.bytes; 351 | data.rx_dgrams += stats.udp_rx.datagrams; 352 | data.tx_dgrams += stats.udp_tx.datagrams; 353 | } 354 | 355 | Err(e) => { 356 | error!("{e}"); 357 | info!( 358 | "[{login_info}] quit after having retried for {} times", 359 | usize::MAX 360 | ); 361 | break; 362 | } 363 | }; 364 | 365 | if self.should_quit() { 366 | break; 367 | } 368 | } 369 | self.post_tunnel_log(format!("[{login_info}] quit").as_str()); 370 | } 371 | 372 | async fn prepare_login_config(&self) -> Result { 373 | let mut transport_cfg = TransportConfig::default(); 374 | transport_cfg.stream_receive_window(quinn::VarInt::from_u32(1024 * 1024)); 375 | transport_cfg.receive_window(quinn::VarInt::from_u32(1024 * 1024 * 2)); 376 | transport_cfg.send_window(1024 * 1024 * 2); 377 | transport_cfg.congestion_controller_factory(Arc::new(congestion::BbrConfig::default())); 378 | transport_cfg.max_concurrent_bidi_streams(VarInt::from_u32(1024)); 379 | 380 | if self.config.quic_timeout_ms > 0 { 381 | let timeout = IdleTimeout::from(VarInt::from_u32(self.config.quic_timeout_ms as u32)); 382 | transport_cfg.max_idle_timeout(Some(timeout)); 383 | transport_cfg.keep_alive_interval(Some(Duration::from_millis( 384 | self.config.quic_timeout_ms * 2 / 3, 385 | ))); 386 | } 387 | 388 | let (tls_client_cfg, domain) = self.parse_client_config_and_domain()?; 389 | let quic_client_cfg = Arc::new(QuicClientConfig::try_from(tls_client_cfg)?); 390 | let mut client_cfg = quinn::ClientConfig::new(quic_client_cfg); 391 | client_cfg.transport_config(Arc::new(transport_cfg)); 392 | 393 | let remote_addr = self.parse_server_addr().await?; 394 | let local_addr = socket_addr_with_unspecified_ip_port(remote_addr.is_ipv6()); 395 | Ok(LoginConfig { 396 | local_addr, 397 | remote_addr, 398 | quinn_client_cfg: client_cfg, 399 | domain, 400 | }) 401 | } 402 | 403 | async fn login( 404 | &self, 405 | index: usize, 406 | endpoint: &Endpoint, 407 | login_info: &LoginInfo, 408 | remote_addr: &SocketAddr, 409 | domain: &str, 410 | ) -> Result { 411 | self.set_and_post_tunnel_state(ClientState::Connecting); 412 | self.post_tunnel_log( 413 | format!( 414 | "{index}:{} connecting, idle_timeout:{}, retry_timeout:{}, cipher:{}, threads:{}", 415 | login_info.format_with_remote_addr(remote_addr), 416 | self.config.quic_timeout_ms, 417 | self.config.wait_before_retry_ms, 418 | self.config.cipher, 419 | self.config.workers, 420 | ) 421 | .as_str(), 422 | ); 423 | 424 | let conn = endpoint.connect(*remote_addr, domain)?.await?; 425 | let (mut quic_send, mut quic_recv) = conn 426 | .open_bi() 427 | .await 428 | .context("open bidirectional connection failed")?; 429 | 430 | self.set_and_post_tunnel_state(ClientState::Connected); 431 | 432 | self.post_tunnel_log( 433 | format!( 434 | "{index}:{} logging in...", 435 | login_info.format_with_remote_addr(remote_addr) 436 | ) 437 | .as_str(), 438 | ); 439 | 440 | let login_msg = TunnelMessage::ReqLogin(login_info.clone()); 441 | TunnelMessage::send(&mut quic_send, &login_msg).await?; 442 | 443 | let resp = TunnelMessage::recv(&mut quic_recv).await?; 444 | if let TunnelMessage::RespFailure(msg) = resp { 445 | bail!( 446 | "{index}:{} failed to login: {msg}", 447 | login_info.format_with_remote_addr(remote_addr) 448 | ); 449 | } 450 | if !resp.is_resp_success() { 451 | bail!( 452 | "{index}:{} unexpected response, failed to login", 453 | login_info.format_with_remote_addr(remote_addr) 454 | ); 455 | } 456 | TunnelMessage::handle_message(&resp)?; 457 | self.post_tunnel_log( 458 | format!( 459 | "{index}:{} login succeeded!", 460 | login_info.format_with_remote_addr(remote_addr) 461 | ) 462 | .as_str(), 463 | ); 464 | Ok(conn) 465 | } 466 | 467 | async fn serve_outbound_tcp( 468 | &mut self, 469 | index: usize, 470 | conn: Connection, 471 | local_server_addr: SocketAddr, 472 | pending_tcp_stream: &mut Option, 473 | ) -> Result<()> { 474 | let tcp_server = { 475 | inner_state!(self, tcp_servers) 476 | .get(&local_server_addr) 477 | .cloned() 478 | }; 479 | 480 | let mut tcp_server = match tcp_server { 481 | Some(server) => server.clone(), 482 | None => self.start_tcp_server(local_server_addr).await?, 483 | }; 484 | 485 | self.post_tunnel_log( 486 | format!( 487 | "{index}:TCP_OUT start serving from {} via {}", 488 | tcp_server.addr(), 489 | conn.remote_address() 490 | ) 491 | .as_str(), 492 | ); 493 | 494 | self.set_and_post_tunnel_state(ClientState::Tunneling); 495 | 496 | TcpTunnel::start( 497 | true, 498 | &conn, 499 | &mut tcp_server, 500 | pending_tcp_stream, 501 | self.config.tcp_timeout_ms, 502 | ) 503 | .await; 504 | 505 | Ok(()) 506 | } 507 | 508 | async fn serve_outbound_udp( 509 | &mut self, 510 | index: usize, 511 | conn: Connection, 512 | local_server_addr: SocketAddr, 513 | ) -> Result<()> { 514 | let udp_server = { 515 | inner_state!(self, udp_servers) 516 | .get(&local_server_addr) 517 | .cloned() 518 | }; 519 | 520 | let udp_server = match udp_server { 521 | Some(server) => server.clone(), 522 | None => self.start_udp_server(local_server_addr).await?, 523 | }; 524 | 525 | self.post_tunnel_log( 526 | format!( 527 | "{index}:UDP_OUT start serving from {} via {}", 528 | udp_server.addr(), 529 | conn.remote_address() 530 | ) 531 | .as_str(), 532 | ); 533 | 534 | self.set_and_post_tunnel_state(ClientState::Tunneling); 535 | 536 | UdpTunnel::start(&conn, udp_server, None, self.config.udp_timeout_ms) 537 | .await 538 | .ok(); 539 | 540 | Ok(()) 541 | } 542 | 543 | async fn serve_inbound_tcp( 544 | &mut self, 545 | index: usize, 546 | conn: Connection, 547 | local_server_addr: SocketAddr, 548 | ) -> Result<()> { 549 | self.post_tunnel_log( 550 | format!( 551 | "{index}:TCP_IN start serving via: {}", 552 | conn.remote_address() 553 | ) 554 | .as_str(), 555 | ); 556 | 557 | self.set_and_post_tunnel_state(ClientState::Tunneling); 558 | TcpTunnel::process(&conn, local_server_addr, self.config.tcp_timeout_ms).await; 559 | 560 | Ok(()) 561 | } 562 | 563 | async fn serve_inbound_udp( 564 | &mut self, 565 | index: usize, 566 | conn: Connection, 567 | local_server_addr: SocketAddr, 568 | ) -> Result<()> { 569 | self.post_tunnel_log( 570 | format!( 571 | "{index}:UDP_IN start serving via: {}", 572 | conn.remote_address() 573 | ) 574 | .as_str(), 575 | ); 576 | 577 | self.set_and_post_tunnel_state(ClientState::Tunneling); 578 | UdpTunnel::process(&conn, local_server_addr, self.config.udp_timeout_ms).await; 579 | 580 | Ok(()) 581 | } 582 | 583 | fn should_quit(&self) -> bool { 584 | let state = self.get_state(); 585 | state == ClientState::Stopping || state == ClientState::Terminated 586 | } 587 | 588 | fn report_traffic_data_in_background(&self) { 589 | let state = self.inner_state.clone(); 590 | tokio::spawn(async move { 591 | let mut interval = 592 | tokio::time::interval(Duration::from_secs(POST_TRAFFIC_DATA_INTERVAL_SECS)); 593 | interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); 594 | 595 | loop { 596 | interval.tick().await; 597 | 598 | let mut rx_bytes = 0; 599 | let mut tx_bytes = 0; 600 | let mut rx_dgrams = 0; 601 | let mut tx_dgrams = 0; 602 | 603 | { 604 | let connections = &state.lock().unwrap().connections; 605 | for conn in connections.values() { 606 | let stats = conn.stats(); 607 | rx_bytes += stats.udp_rx.bytes; 608 | tx_bytes += stats.udp_tx.bytes; 609 | rx_dgrams += stats.udp_rx.datagrams; 610 | tx_dgrams += stats.udp_tx.datagrams; 611 | } 612 | } 613 | 614 | { 615 | let total_traffic_data = &&state.lock().unwrap().total_traffic_data; 616 | rx_bytes += total_traffic_data.rx_bytes; 617 | tx_bytes += total_traffic_data.tx_bytes; 618 | rx_dgrams += total_traffic_data.rx_dgrams; 619 | tx_dgrams += total_traffic_data.tx_dgrams; 620 | } 621 | 622 | let state = state.lock().unwrap(); 623 | let client_state = state.client_state.clone(); 624 | let data = TunnelTraffic { 625 | rx_bytes, 626 | tx_bytes, 627 | rx_dgrams, 628 | tx_dgrams, 629 | }; 630 | 631 | info!("traffic log, rx_bytes:{rx_bytes}, tx_bytes:{tx_bytes}, rx_dgrams:{rx_dgrams}, tx_dgrams:{tx_dgrams}"); 632 | state.post_tunnel_info(TunnelInfo::new( 633 | TunnelInfoType::TunnelTraffic, 634 | Box::new(data), 635 | )); 636 | 637 | if client_state == ClientState::Stopping || client_state == ClientState::Terminated 638 | { 639 | break; 640 | } 641 | } 642 | }); 643 | } 644 | 645 | fn get_crypto_provider(&self, cipher: &SupportedCipherSuite) -> Arc { 646 | let default_provider = rustls::crypto::ring::default_provider(); 647 | let mut cipher_suites = vec![*cipher]; 648 | // Quinn assumes that the cipher suites contain this one 649 | cipher_suites.push(cipher_suite::TLS13_AES_128_GCM_SHA256); 650 | Arc::new(rustls::crypto::CryptoProvider { 651 | cipher_suites, 652 | ..default_provider 653 | }) 654 | } 655 | 656 | fn create_client_config_builder( 657 | &self, 658 | cipher: &SupportedCipherSuite, 659 | ) -> std::result::Result< 660 | rustls::ConfigBuilder, 661 | rustls::Error, 662 | > { 663 | let cfg_builder = 664 | rustls::ClientConfig::builder_with_provider(self.get_crypto_provider(cipher)) 665 | .with_protocol_versions(&[&rustls::version::TLS13]) 666 | .unwrap(); 667 | Ok(cfg_builder) 668 | } 669 | 670 | fn parse_client_config_and_domain(&self) -> Result<(rustls::ClientConfig, String)> { 671 | let cipher = *SelectedCipherSuite::from_str(&self.config.cipher).map_err(|_| { 672 | rustls::Error::General(format!("invalid cipher: {}", self.config.cipher)) 673 | })?; 674 | 675 | if self.config.cert_path.is_empty() { 676 | if !Self::is_ip_addr(&self.config.server_addr) { 677 | let domain = match self.config.server_addr.rfind(':') { 678 | Some(colon_index) => self.config.server_addr[0..colon_index].to_string(), 679 | None => self.config.server_addr.to_string(), 680 | }; 681 | 682 | let client_config = self 683 | .create_client_config_builder(&cipher)? 684 | .with_platform_verifier()? 685 | .with_no_client_auth(); 686 | 687 | return Ok((client_config, domain)); 688 | } 689 | 690 | let client_config = self 691 | .create_client_config_builder(&cipher)? 692 | .dangerous() 693 | .with_custom_certificate_verifier(Arc::new(InsecureCertVerifier::new( 694 | self.get_crypto_provider(&cipher), 695 | ))) 696 | .with_no_client_auth(); 697 | 698 | static ONCE: Once = Once::new(); 699 | ONCE.call_once(|| { 700 | warn!( 701 | "No certificate is provided for verification, domain \"localhost\" is assumed" 702 | ); 703 | }); 704 | return Ok((client_config, "localhost".to_string())); 705 | } 706 | 707 | // when client config provides a certificate 708 | let certs = pem_util::load_certificates_from_pem(self.config.cert_path.as_str()) 709 | .context("failed to read from cert file")?; 710 | if certs.is_empty() { 711 | log_and_bail!( 712 | "No certificates found in provided file: {}", 713 | self.config.cert_path 714 | ); 715 | } 716 | let mut roots = RootCertStore::empty(); 717 | // save all certificates in the certificate chain to the trust list 718 | for cert in &certs { 719 | roots.add(cert.clone()).context(format!( 720 | "failed to add certificate from file: {}", 721 | self.config.cert_path 722 | ))?; 723 | } 724 | 725 | // for self-signed certificates, generating IP-based TLS certificates is not difficult 726 | let domain_or_ip = match self.config.server_addr.rfind(':') { 727 | Some(colon_index) => self.config.server_addr[0..colon_index].to_string(), 728 | None => self.config.server_addr.to_string(), 729 | }; 730 | 731 | Ok(( 732 | self.create_client_config_builder(&cipher)? 733 | .with_root_certificates(roots) 734 | .with_no_client_auth(), 735 | domain_or_ip, 736 | )) 737 | } 738 | 739 | pub fn get_state(&self) -> ClientState { 740 | inner_state!(self, client_state).clone() 741 | } 742 | 743 | fn is_ip_addr(addr: &str) -> bool { 744 | addr.parse::().is_ok() 745 | } 746 | 747 | async fn parse_server_addr(&self) -> Result { 748 | let addr = self.config.server_addr.as_str(); 749 | let sock_addr: Result = addr.parse().context("error will be ignored"); 750 | 751 | if sock_addr.is_ok() { 752 | return sock_addr; 753 | } 754 | 755 | let mut domain = addr; 756 | let mut port = DEFAULT_SERVER_PORT; 757 | let pos = addr.rfind(':'); 758 | if let Some(pos) = pos { 759 | port = addr[(pos + 1)..] 760 | .parse() 761 | .with_context(|| format!("invalid address: {}", addr))?; 762 | domain = &addr[..pos]; 763 | } 764 | 765 | for dot in &self.config.dot_servers { 766 | if let Ok(ip) = Self::lookup_server_ip(domain, dot, vec![]).await { 767 | return Ok(SocketAddr::new(ip, port)); 768 | } 769 | } 770 | 771 | if let Ok(ip) = Self::lookup_server_ip(domain, "", self.config.dns_servers.clone()).await { 772 | return Ok(SocketAddr::new(ip, port)); 773 | } 774 | 775 | if let Ok(ip) = Self::lookup_server_ip(domain, "", vec![]).await { 776 | return Ok(SocketAddr::new(ip, port)); 777 | } 778 | 779 | bail!("failed to resolve domain: {domain}"); 780 | } 781 | 782 | async fn lookup_server_ip( 783 | domain: &str, 784 | dot_server: &str, 785 | name_servers: Vec, 786 | ) -> Result { 787 | let dns_config = DNSResolverConfig { 788 | strategy: DNSResolverLookupIpStrategy::Ipv6thenIpv4, 789 | num_conccurent_reqs: 3, 790 | ordering: DNSQueryOrdering::QueryStatistics, 791 | }; 792 | 793 | let resolver = if !dot_server.is_empty() { 794 | dns::resolver2(dot_server, vec![], dns_config) 795 | } else if !name_servers.is_empty() { 796 | dns::resolver2("", name_servers, dns_config) 797 | } else { 798 | dns::resolver2("", vec![], dns_config) 799 | }; 800 | 801 | let ip = resolver.await.lookup_first(domain).await?; 802 | info!("resolved {domain} to {ip}"); 803 | Ok(ip) 804 | } 805 | 806 | fn post_tunnel_log(&self, msg: &str) { 807 | info!("{msg}"); 808 | let state = self.inner_state.lock().unwrap(); 809 | state.post_tunnel_info(TunnelInfo::new( 810 | TunnelInfoType::TunnelLog, 811 | Box::new(format!( 812 | "{} {msg}", 813 | chrono::Local::now().format(TIME_FORMAT) 814 | )), 815 | )); 816 | } 817 | 818 | fn set_and_post_tunnel_state(&self, client_state: ClientState) { 819 | let mut state = self.inner_state.lock().unwrap(); 820 | state.client_state = client_state.clone(); 821 | state.post_tunnel_info(TunnelInfo::new( 822 | TunnelInfoType::TunnelState, 823 | Box::new(client_state), 824 | )); 825 | } 826 | 827 | pub fn set_on_info_listener(&self, callback: impl FnMut(&str) + 'static + Send + Sync) { 828 | inner_state!(self, tunnel_info_bridge).set_listener(callback); 829 | } 830 | 831 | pub fn has_on_info_listener(&self) -> bool { 832 | inner_state!(self, tunnel_info_bridge).has_listener() 833 | } 834 | 835 | pub fn set_enable_on_info_report(&self, enable: bool) { 836 | info!("set_enable_on_info_report, enable:{enable}"); 837 | inner_state!(self, on_info_report_enabled) = enable; 838 | } 839 | } 840 | 841 | #[derive(Debug)] 842 | struct InsecureCertVerifier(Arc); 843 | 844 | impl InsecureCertVerifier { 845 | pub fn new(crypto: Arc) -> Self { 846 | Self(crypto) 847 | } 848 | } 849 | 850 | impl rustls::client::danger::ServerCertVerifier for InsecureCertVerifier { 851 | fn verify_tls12_signature( 852 | &self, 853 | message: &[u8], 854 | cert: &rustls::pki_types::CertificateDer<'_>, 855 | dss: &rustls::DigitallySignedStruct, 856 | ) -> std::prelude::v1::Result 857 | { 858 | rustls::crypto::verify_tls12_signature( 859 | message, 860 | cert, 861 | dss, 862 | &self.0.signature_verification_algorithms, 863 | ) 864 | } 865 | 866 | fn verify_tls13_signature( 867 | &self, 868 | message: &[u8], 869 | cert: &rustls::pki_types::CertificateDer<'_>, 870 | dss: &rustls::DigitallySignedStruct, 871 | ) -> std::prelude::v1::Result 872 | { 873 | rustls::crypto::verify_tls12_signature( 874 | message, 875 | cert, 876 | dss, 877 | &self.0.signature_verification_algorithms, 878 | ) 879 | } 880 | 881 | fn supported_verify_schemes(&self) -> Vec { 882 | self.0.signature_verification_algorithms.supported_schemes() 883 | } 884 | 885 | fn verify_server_cert( 886 | &self, 887 | _end_entity: &rustls::pki_types::CertificateDer<'_>, 888 | _intermediates: &[rustls::pki_types::CertificateDer<'_>], 889 | _server_name: &rustls::pki_types::ServerName<'_>, 890 | _ocsp_response: &[u8], 891 | _now: rustls::pki_types::UnixTime, 892 | ) -> std::prelude::v1::Result { 893 | static ONCE: Once = Once::new(); 894 | ONCE.call_once(|| { 895 | warn!("======================================= WARNING ======================================"); 896 | warn!("Connecting to a server without verifying its certificate is DANGEROUS!!!"); 897 | warn!("Provide the self-signed certificate for verification or connect with a domain name"); 898 | warn!("======================= Be cautious, this is for TEST only!!! ========================"); 899 | }); 900 | Ok(ServerCertVerified::assertion()) 901 | } 902 | } 903 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod pem_util; 3 | mod server; 4 | mod tcp; 5 | mod tunnel_info_bridge; 6 | mod tunnel_message; 7 | mod udp; 8 | 9 | use anyhow::{bail, Context, Result}; 10 | use byte_pool::Block; 11 | use byte_pool::BytePool; 12 | pub use client::Client; 13 | pub use client::ClientState; 14 | use lazy_static::lazy_static; 15 | use log::error; 16 | use rs_utilities::log_and_bail; 17 | use rustls::crypto::ring::cipher_suite; 18 | use serde::Deserialize; 19 | use serde::Serialize; 20 | pub use server::Server; 21 | use std::fmt::Display; 22 | use std::net::IpAddr; 23 | use std::net::Ipv4Addr; 24 | use std::net::Ipv6Addr; 25 | use std::{net::SocketAddr, ops::Deref}; 26 | pub use tcp::tcp_server::TcpServer; 27 | pub use tunnel_message::LoginInfo; 28 | use udp::udp_server::UdpServer; 29 | 30 | extern crate bincode; 31 | extern crate pretty_env_logger; 32 | 33 | pub const TUNNEL_MODE_IN: &str = "IN"; 34 | pub const TUNNEL_MODE_OUT: &str = "OUT"; 35 | pub const UDP_PACKET_SIZE: usize = 1500; 36 | 37 | lazy_static! { 38 | static ref BUFFER_POOL: BytePool::> = BytePool::>::new(); 39 | } 40 | type PooledBuffer = Block<'static, Vec>; 41 | 42 | pub const SUPPORTED_CIPHER_SUITE_STRS: &[&str] = &[ 43 | "chacha20-poly1305", 44 | "aes-256-gcm", 45 | "aes-128-gcm", 46 | // the following ciphers don't work at the moement, will look into it later 47 | // "ecdhe-ecdsa-aes256-gcm", 48 | // "ecdhe-ecdsa-aes128-gcm", 49 | // "ecdhe-ecdsa-chacha20-poly1305", 50 | // "ecdhe-rsa-aes256-gcm", 51 | // "ecdhe-rsa-aes128-gcm", 52 | // "ecdhe-rsa-chacha20-poly1305", 53 | ]; 54 | 55 | pub static SUPPORTED_CIPHER_SUITES: &[rustls::SupportedCipherSuite] = &[ 56 | cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, 57 | cipher_suite::TLS13_AES_256_GCM_SHA384, 58 | cipher_suite::TLS13_AES_128_GCM_SHA256, 59 | ]; 60 | 61 | pub(crate) struct SelectedCipherSuite(rustls::SupportedCipherSuite); 62 | 63 | impl std::str::FromStr for SelectedCipherSuite { 64 | type Err = (); 65 | 66 | fn from_str(s: &str) -> Result { 67 | match s { 68 | "chacha20-poly1305" => Ok(SelectedCipherSuite( 69 | cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, 70 | )), 71 | "aes-256-gcm" => Ok(SelectedCipherSuite(cipher_suite::TLS13_AES_256_GCM_SHA384)), 72 | "aes-128-gcm" => Ok(SelectedCipherSuite(cipher_suite::TLS13_AES_128_GCM_SHA256)), 73 | // "ecdhe-ecdsa-aes256-gcm" => Ok(SelectedCipherSuite( 74 | // rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 75 | // )), 76 | // "ecdhe-ecdsa-aes128-gcm" => Ok(SelectedCipherSuite( 77 | // rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 78 | // )), 79 | // "ecdhe-ecdsa-chacha20-poly1305" => Ok(SelectedCipherSuite( 80 | // rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 81 | // )), 82 | // "ecdhe-rsa-aes256-gcm" => Ok(SelectedCipherSuite( 83 | // rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 84 | // )), 85 | // "ecdhe-rsa-aes128-gcm" => Ok(SelectedCipherSuite( 86 | // rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 87 | // )), 88 | // "ecdhe-rsa-chacha20-poly1305" => Ok(SelectedCipherSuite( 89 | // rustls::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 90 | // )), 91 | _ => Ok(SelectedCipherSuite( 92 | cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, 93 | )), 94 | } 95 | } 96 | } 97 | 98 | impl Deref for SelectedCipherSuite { 99 | type Target = rustls::SupportedCipherSuite; 100 | fn deref(&self) -> &Self::Target { 101 | &self.0 102 | } 103 | } 104 | 105 | #[derive(Debug)] 106 | pub struct TcpTunnelOutInfo { 107 | conn: quinn::Connection, 108 | upstream_addr: SocketAddr, 109 | } 110 | 111 | #[derive(Debug)] 112 | pub struct TcpTunnelInInfo { 113 | conn: quinn::Connection, 114 | tcp_server: TcpServer, 115 | } 116 | 117 | #[derive(Debug)] 118 | pub struct UdpTunnelOutInfo { 119 | conn: quinn::Connection, 120 | upstream_addr: SocketAddr, 121 | } 122 | 123 | #[derive(Debug)] 124 | pub struct UdpTunnelInInfo { 125 | conn: quinn::Connection, 126 | udp_server: UdpServer, 127 | } 128 | 129 | #[derive(Debug)] 130 | pub enum TunnelType { 131 | TcpOut(TcpTunnelOutInfo), 132 | TcpIn(TcpTunnelInInfo), 133 | UdpOut(UdpTunnelOutInfo), 134 | UdpIn(UdpTunnelInInfo), 135 | } 136 | 137 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 138 | pub enum TunnelMode { 139 | In, 140 | Out, 141 | } 142 | 143 | impl Display for TunnelMode { 144 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 145 | match self { 146 | Self::In => write!(f, "IN"), 147 | Self::Out => write!(f, "OUT"), 148 | } 149 | } 150 | } 151 | 152 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 153 | pub enum UpstreamType { 154 | Tcp, 155 | Udp, 156 | } 157 | 158 | impl Display for UpstreamType { 159 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 160 | match self { 161 | Self::Tcp => write!(f, "TCP"), 162 | Self::Udp => write!(f, "UDP"), 163 | } 164 | } 165 | } 166 | 167 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 168 | pub struct Upstream { 169 | pub upstream_addr: Option, 170 | pub upstream_type: UpstreamType, 171 | } 172 | 173 | impl Display for Upstream { 174 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 175 | match self.upstream_addr { 176 | Some(addr) => write!(f, "{}", addr), 177 | None => write!(f, "PeerDefault"), 178 | } 179 | } 180 | } 181 | 182 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 183 | pub struct TunnelConfig { 184 | pub mode: TunnelMode, 185 | pub local_server_addr: Option, 186 | pub upstream: Upstream, 187 | } 188 | 189 | #[derive(Debug, Default, Clone)] 190 | pub struct ClientConfig { 191 | pub cert_path: String, 192 | pub cipher: String, 193 | pub server_addr: String, 194 | pub password: String, 195 | pub wait_before_retry_ms: u64, 196 | pub quic_timeout_ms: u64, 197 | pub tcp_timeout_ms: u64, 198 | pub udp_timeout_ms: u64, 199 | pub tunnels: Vec, 200 | pub dot_servers: Vec, 201 | pub dns_servers: Vec, 202 | pub workers: usize, 203 | } 204 | 205 | #[derive(Debug, Clone)] 206 | pub struct ServerConfig { 207 | pub addr: String, 208 | pub password: String, 209 | pub cert_path: String, 210 | pub key_path: String, 211 | pub quic_timeout_ms: u64, 212 | pub tcp_timeout_ms: u64, 213 | pub udp_timeout_ms: u64, 214 | 215 | /// for TunnelOut only 216 | pub default_tcp_upstream: Option, 217 | pub default_udp_upstream: Option, 218 | 219 | /// 0.0.0.0:3515 220 | pub dashboard_server: String, 221 | /// user:password 222 | pub dashboard_server_credential: String, 223 | } 224 | 225 | impl ClientConfig { 226 | #[allow(clippy::too_many_arguments)] 227 | pub fn create( 228 | server_addr: &str, 229 | password: &str, 230 | cert: &str, 231 | cipher: &str, 232 | tcp_addr_mappings: &str, 233 | udp_addr_mappings: &str, 234 | dot: &str, 235 | dns: &str, 236 | workers: usize, 237 | wait_before_retry_ms: u64, 238 | mut quic_timeout_ms: u64, 239 | mut tcp_timeout_ms: u64, 240 | mut udp_timeout_ms: u64, 241 | ) -> Result { 242 | if tcp_addr_mappings.is_empty() && udp_addr_mappings.is_empty() { 243 | log_and_bail!("must specify either --tcp-mappings or --udp-mappings, or both"); 244 | } 245 | 246 | if quic_timeout_ms == 0 { 247 | quic_timeout_ms = 30000; 248 | } 249 | if tcp_timeout_ms == 0 { 250 | tcp_timeout_ms = 30000; 251 | } 252 | if udp_timeout_ms == 0 { 253 | udp_timeout_ms = 5000; 254 | } 255 | 256 | let mut config = ClientConfig { 257 | cert_path: cert.to_string(), 258 | cipher: cipher.to_string(), 259 | server_addr: if !server_addr.contains(':') { 260 | format!("127.0.0.1:{server_addr}") 261 | } else { 262 | server_addr.to_string() 263 | }, 264 | password: password.to_string(), 265 | workers: if workers > 0 { 266 | workers 267 | } else { 268 | num_cpus::get() 269 | }, 270 | wait_before_retry_ms, 271 | quic_timeout_ms, 272 | tcp_timeout_ms, 273 | udp_timeout_ms, 274 | dot_servers: dot.split(',').map(|s| s.to_string()).collect(), 275 | dns_servers: dns.split(',').map(|s| s.to_string()).collect(), 276 | ..ClientConfig::default() 277 | }; 278 | 279 | parse_addr_mappings(tcp_addr_mappings, UpstreamType::Tcp, &mut config.tunnels)?; 280 | parse_addr_mappings(udp_addr_mappings, UpstreamType::Udp, &mut config.tunnels)?; 281 | 282 | Ok(config) 283 | } 284 | } 285 | 286 | fn parse_addr_mappings( 287 | mappings: &str, 288 | upstream_type: UpstreamType, 289 | v: &mut Vec, 290 | ) -> Result<()> { 291 | if mappings.is_empty() { 292 | return Ok(()); 293 | } 294 | 295 | for mapping in mappings.split(',') { 296 | let parts: Vec<&str> = mapping.split('^').collect(); 297 | if parts.len() != 3 { 298 | log_and_bail!("Invalid mapping format, expected TYPE^SRC^DEST"); 299 | } 300 | 301 | let tunnel_mode = parts[0]; 302 | if tunnel_mode != "OUT" && tunnel_mode != "IN" { 303 | log_and_bail!("Invalid tunnel type, expected OUT or IN"); 304 | } 305 | 306 | let parse_addr = |addr: &str| -> Result> { 307 | if addr == "ANY" { 308 | return Ok(None); 309 | } 310 | 311 | // Handle port-only case 312 | let port = addr.parse::(); 313 | if let Ok(port) = port { 314 | return Ok(Some(SocketAddr::new( 315 | IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 316 | port, 317 | ))); 318 | } 319 | 320 | // Parse full SocketAddr 321 | Ok(Some(addr.parse().with_context(|| { 322 | format!("Invalid address format '{addr}', expected IP:PORT or PORT") 323 | })?)) 324 | }; 325 | 326 | let local_server_addr = parse_addr(parts[1])?; 327 | if local_server_addr.is_none() { 328 | log_and_bail!("'ANY' is not allowed as local_server_addr"); 329 | } 330 | let upstream_addr = parse_addr(parts[2])?; 331 | 332 | v.push(TunnelConfig { 333 | mode: if tunnel_mode == "IN" { 334 | TunnelMode::In 335 | } else { 336 | TunnelMode::Out 337 | }, 338 | upstream: Upstream { 339 | upstream_addr, 340 | upstream_type: upstream_type.clone(), 341 | }, 342 | local_server_addr, 343 | }); 344 | } 345 | 346 | Ok(()) 347 | } 348 | 349 | pub fn socket_addr_with_unspecified_ip_port(ipv6: bool) -> SocketAddr { 350 | if ipv6 { 351 | SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0) 352 | } else { 353 | SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0) 354 | } 355 | } 356 | 357 | #[cfg(target_os = "android")] 358 | #[allow(non_snake_case)] 359 | pub mod android { 360 | extern crate jni; 361 | 362 | use jni::sys::{jlong, jstring}; 363 | use log::{error, info}; 364 | 365 | use self::jni::objects::{JClass, JObject, JString}; 366 | use self::jni::sys::{jboolean, jint, JNI_TRUE, JNI_VERSION_1_6}; 367 | use self::jni::{JNIEnv, JavaVM}; 368 | use super::*; 369 | use std::os::raw::c_void; 370 | use std::sync::{Arc, Mutex}; 371 | use std::thread; 372 | 373 | #[no_mangle] 374 | pub extern "system" fn JNI_OnLoad(_vm: JavaVM, _: *mut c_void) -> jint { 375 | JNI_VERSION_1_6 376 | } 377 | 378 | #[no_mangle] 379 | pub unsafe extern "C" fn Java_net_neevek_omnip_RsTunc_initCertificateVerifier( 380 | mut env: JNIEnv, 381 | _: JClass, 382 | context: JObject, 383 | ) { 384 | if let Err(e) = rustls_platform_verifier::android::init_hosted( 385 | &mut env, 386 | JObject::try_from(context).unwrap(), 387 | ) { 388 | error!("failed to init rustls_platform_verifier: {}", e); 389 | } else { 390 | info!("initializing rustls_platform_verifier succeeded!"); 391 | } 392 | } 393 | 394 | #[no_mangle] 395 | pub unsafe extern "C" fn Java_net_neevek_omnip_RsTunc_nativeInitLogger( 396 | mut env: JNIEnv, 397 | _: JClass, 398 | jlogLevel: JString, 399 | ) -> jboolean { 400 | let log_level = match convert_jstring(&mut env, jlogLevel).as_str() { 401 | "T" => "trace", 402 | "D" => "debug", 403 | "I" => "info", 404 | "W" => "warn", 405 | "E" => "error", 406 | _ => "info", 407 | }; 408 | let log_filter = format!("rstun={},rs_utilities={}", log_level, log_level); 409 | rs_utilities::LogHelper::init_logger("rstunc", log_filter.as_str()); 410 | return JNI_TRUE; 411 | } 412 | 413 | #[no_mangle] 414 | pub unsafe extern "C" fn Java_net_neevek_omnip_RsTunc_nativeCreate( 415 | mut env: JNIEnv, 416 | _: JClass, 417 | jserverAddr: JString, 418 | jtcpMappings: JString, 419 | judpMappings: JString, 420 | jdotServer: JString, 421 | jdnsServer: JString, 422 | jpassword: JString, 423 | jcertFilePath: JString, 424 | jcipher: JString, 425 | jworkers: jint, 426 | jwaitBeforeRetryMs: jint, 427 | jquicTimeoutMs: jint, 428 | ) -> jlong { 429 | let server_addr = convert_jstring(&mut env, jserverAddr); 430 | let tcp_mappings = convert_jstring(&mut env, jtcpMappings); 431 | let udp_mappings = convert_jstring(&mut env, judpMappings); 432 | let dot_server = convert_jstring(&mut env, jdotServer); 433 | let dns_server = convert_jstring(&mut env, jdnsServer); 434 | let password = convert_jstring(&mut env, jpassword); 435 | let cert_file_path = convert_jstring(&mut env, jcertFilePath); 436 | let cipher = convert_jstring(&mut env, jcipher); 437 | 438 | match ClientConfig::create( 439 | &server_addr, 440 | &password, 441 | &cert_file_path, 442 | &cipher, 443 | &tcp_mappings, 444 | &udp_mappings, 445 | &dot_server, 446 | &dns_server, 447 | jworkers as usize, 448 | jwaitBeforeRetryMs as u64, 449 | jquicTimeoutMs as u64, 450 | 0, // tcp_timeout_ms - use default 451 | 0, // udp_timeout_ms - use default 452 | ) { 453 | Ok(client_config) => { 454 | let client = Client::new(client_config); 455 | let client = Box::into_raw(Box::new(client)); 456 | client as jlong 457 | } 458 | Err(e) => { 459 | error!("failed to create client: {}", e); 460 | 0 461 | } 462 | } 463 | } 464 | 465 | #[no_mangle] 466 | pub unsafe extern "C" fn Java_net_neevek_omnip_RsTunc_nativeStop( 467 | _env: JNIEnv, 468 | _: JClass, 469 | client_ptr: jlong, 470 | ) { 471 | if client_ptr != 0 { 472 | let _boxed_client = Box::from_raw(client_ptr as *mut Client); 473 | } 474 | } 475 | 476 | #[no_mangle] 477 | pub unsafe extern "C" fn Java_net_neevek_omnip_RsTunc_nativeStartTunnelling( 478 | _env: JNIEnv, 479 | _: JClass, 480 | client_ptr: jlong, 481 | ) { 482 | if client_ptr == 0 { 483 | return; 484 | } 485 | 486 | thread::spawn(move || { 487 | let mut client = (&mut *(client_ptr as *mut Arc>)) 488 | .lock() 489 | .unwrap(); 490 | client.start_tunneling(); 491 | }); 492 | } 493 | 494 | #[no_mangle] 495 | pub unsafe extern "C" fn Java_net_neevek_omnip_RsTunc_nativeGetState( 496 | env: JNIEnv, 497 | _: JClass, 498 | client_ptr: jlong, 499 | ) -> jstring { 500 | if client_ptr == 0 { 501 | return env.new_string("").unwrap().into_raw(); 502 | } 503 | 504 | let client = (&mut *(client_ptr as *mut Arc>)) 505 | .lock() 506 | .unwrap(); 507 | env.new_string(client.get_state().to_string()) 508 | .unwrap() 509 | .into_raw() 510 | } 511 | 512 | #[no_mangle] 513 | pub unsafe extern "C" fn Java_net_neevek_omnip_RsTunc_nativeSetEnableOnInfoReport( 514 | env: JNIEnv, 515 | jobj: JClass, 516 | client_ptr: jlong, 517 | enable: jboolean, 518 | ) { 519 | if client_ptr == 0 { 520 | return; 521 | } 522 | 523 | let client = (&mut *(client_ptr as *mut Arc>)) 524 | .lock() 525 | .unwrap(); 526 | let bool_enable = enable == 1; 527 | if bool_enable && !client.has_on_info_listener() { 528 | let jvm = env.get_java_vm().unwrap(); 529 | let jobj_global_ref = env.new_global_ref(jobj).unwrap(); 530 | client.set_on_info_listener(move |data: &str| { 531 | let mut env = jvm.attach_current_thread().unwrap(); 532 | if let Ok(s) = env.new_string(data) { 533 | env.call_method( 534 | &jobj_global_ref, 535 | "onInfo", 536 | "(Ljava/lang/String;)V", 537 | &[(&s).into()], 538 | ) 539 | .unwrap(); 540 | } 541 | }); 542 | } 543 | 544 | client.set_enable_on_info_report(bool_enable); 545 | } 546 | 547 | fn convert_jstring(env: &mut JNIEnv, jstr: JString) -> String { 548 | if !jstr.is_null() { 549 | env.get_string(&jstr).unwrap().to_string_lossy().to_string() 550 | } else { 551 | String::from("") 552 | } 553 | } 554 | } 555 | -------------------------------------------------------------------------------- /src/pem_util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::bail; 2 | use anyhow::Context; 3 | use anyhow::Result; 4 | use rustls::pki_types::CertificateDer; 5 | use rustls::pki_types::PrivateKeyDer; 6 | use rustls_pemfile::Item; 7 | use std::fs; 8 | use std::path::PathBuf; 9 | use std::{fs::File, io::BufReader}; 10 | 11 | pub fn load_certificates_from_pem(path: &str) -> Result>> { 12 | let file_path = PathBuf::from(path); 13 | let cert_buf = fs::read(file_path).context("reading cert failed")?; 14 | let cert_buf = &mut cert_buf.as_ref(); 15 | let certs = rustls_pemfile::certs(cert_buf); 16 | Ok(certs.filter_map(Result::ok).collect()) 17 | } 18 | 19 | pub fn load_private_key_from_pem(path: &str) -> Result> { 20 | let file = File::open(path)?; 21 | let mut reader = BufReader::new(&file); 22 | 23 | let key = match rustls_pemfile::read_one(&mut reader).context("failed to read private key")? { 24 | Some(Item::Pkcs1Key(key)) => PrivateKeyDer::Pkcs1(key), 25 | Some(Item::Pkcs8Key(key)) => PrivateKeyDer::Pkcs8(key), 26 | Some(Item::Sec1Key(key)) => PrivateKeyDer::Sec1(key), 27 | _ => bail!("unexpected private key"), 28 | }; 29 | 30 | Ok(key) 31 | } 32 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::tcp::tcp_server::{TcpMessage, TcpSender}; 2 | use crate::tcp::tcp_tunnel::TcpTunnel; 3 | use crate::tunnel_message::TunnelMessage; 4 | use crate::udp::udp_server::{UdpMessage, UdpSender}; 5 | use crate::udp::{udp_server::UdpServer, udp_tunnel::UdpTunnel}; 6 | use crate::{ 7 | pem_util, LoginInfo, ServerConfig, TcpServer, TcpTunnelInInfo, TcpTunnelOutInfo, TunnelMode, 8 | TunnelType, UdpTunnelInInfo, UdpTunnelOutInfo, UpstreamType, SUPPORTED_CIPHER_SUITES, 9 | }; 10 | use anyhow::{bail, Context, Result}; 11 | use log::{debug, error, info, warn}; 12 | use quinn::crypto::rustls::QuicServerConfig; 13 | use quinn::{congestion, Connection, Endpoint, TransportConfig}; 14 | use quinn_proto::{IdleTimeout, VarInt}; 15 | use rs_utilities::log_and_bail; 16 | use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; 17 | use std::net::SocketAddr; 18 | use std::sync::{Arc, Mutex, Once}; 19 | use tokio::time::Duration; 20 | 21 | #[derive(Debug, Clone)] 22 | struct ConnectedTcpInSession { 23 | conn: Connection, 24 | sender: TcpSender, 25 | } 26 | 27 | #[derive(Debug, Clone)] 28 | struct ConnectedUdpInSession { 29 | conn: Connection, 30 | sender: UdpSender, 31 | } 32 | 33 | #[derive(Debug)] 34 | struct State { 35 | config: ServerConfig, 36 | endpoint: Option, 37 | tcp_sessions: Vec, 38 | udp_sessions: Vec, 39 | } 40 | 41 | impl State { 42 | pub fn new(config: ServerConfig) -> Self { 43 | State { 44 | config, 45 | endpoint: None, 46 | tcp_sessions: Vec::new(), 47 | udp_sessions: Vec::new(), 48 | } 49 | } 50 | } 51 | 52 | #[derive(Debug)] 53 | pub struct Server { 54 | inner_state: Arc>, 55 | } 56 | 57 | macro_rules! inner_state { 58 | ($self:ident, $field:ident) => { 59 | (*$self.inner_state.lock().unwrap()).$field 60 | }; 61 | } 62 | 63 | impl Server { 64 | pub fn new(config: ServerConfig) -> Self { 65 | Server { 66 | inner_state: Arc::new(Mutex::new(State::new(config))), 67 | } 68 | } 69 | 70 | pub fn bind(&mut self) -> Result { 71 | let mut state = self.inner_state.lock().unwrap(); 72 | let config = state.config.clone(); 73 | let addr: SocketAddr = config 74 | .addr 75 | .parse() 76 | .context(format!("invalid address: {}", config.addr))?; 77 | 78 | let quinn_server_cfg = Self::load_quinn_server_config(&config)?; 79 | let endpoint = quinn::Endpoint::server(quinn_server_cfg, addr).inspect_err(|e| { 80 | error!("failed to bind tunnel server on address: {addr}, err: {e}"); 81 | })?; 82 | 83 | info!( 84 | "tunnel server is bound on address: {}, idle_timeout: {}", 85 | endpoint.local_addr()?, 86 | config.quic_timeout_ms 87 | ); 88 | 89 | let ep = endpoint.clone(); 90 | tokio::spawn(async move { 91 | loop { 92 | tokio::time::sleep(Duration::from_secs(3600 * 24)).await; 93 | match Self::load_quinn_server_config(&config) { 94 | Ok(quinn_server_cfg) => { 95 | info!("updated quinn server config!"); 96 | ep.set_server_config(Some(quinn_server_cfg)); 97 | } 98 | Err(e) => { 99 | error!("failed to load quinn server config:{e}"); 100 | } 101 | } 102 | } 103 | }); 104 | 105 | state.endpoint = Some(endpoint); 106 | Ok(addr) 107 | } 108 | 109 | fn load_quinn_server_config(config: &ServerConfig) -> Result { 110 | let (certs, key) = 111 | Self::read_certs_and_key(config.cert_path.as_str(), config.key_path.as_str()) 112 | .context("failed to read certificate or key")?; 113 | 114 | let default_provider = rustls::crypto::ring::default_provider(); 115 | let provider = rustls::crypto::CryptoProvider { 116 | cipher_suites: SUPPORTED_CIPHER_SUITES.into(), 117 | ..default_provider 118 | }; 119 | 120 | let tls_server_cfg = rustls::ServerConfig::builder_with_provider(provider.into()) 121 | .with_protocol_versions(&[&rustls::version::TLS13]) 122 | .unwrap() 123 | .with_no_client_auth() 124 | .with_single_cert(certs, key) 125 | .unwrap(); 126 | 127 | let mut transport_cfg = TransportConfig::default(); 128 | transport_cfg.stream_receive_window(quinn::VarInt::from_u32(1024 * 1024)); 129 | transport_cfg.receive_window(quinn::VarInt::from_u32(1024 * 1024 * 2)); 130 | transport_cfg.send_window(1024 * 1024 * 2); 131 | transport_cfg.congestion_controller_factory(Arc::new(congestion::BbrConfig::default())); 132 | if config.quic_timeout_ms > 0 { 133 | let timeout = IdleTimeout::from(VarInt::from_u32(config.quic_timeout_ms as u32)); 134 | transport_cfg.max_idle_timeout(Some(timeout)); 135 | transport_cfg 136 | .keep_alive_interval(Some(Duration::from_millis(config.quic_timeout_ms * 2 / 3))); 137 | } 138 | transport_cfg.max_concurrent_bidi_streams(VarInt::from_u32(1024)); 139 | 140 | let quic_server_cfg = Arc::new(QuicServerConfig::try_from(tls_server_cfg)?); 141 | let mut quinn_server_cfg = quinn::ServerConfig::with_crypto(quic_server_cfg); 142 | quinn_server_cfg.transport = Arc::new(transport_cfg); 143 | Ok(quinn_server_cfg) 144 | } 145 | 146 | pub async fn serve(&self) -> Result<()> { 147 | let state = self.inner_state.clone(); 148 | tokio::spawn(async move { 149 | let mut interval = tokio::time::interval(Duration::from_secs(2)); 150 | loop { 151 | interval.tick().await; 152 | Self::clear_expired_sessions(state.clone()); 153 | } 154 | }); 155 | 156 | let endpoint = inner_state!(self, endpoint).take().context("failed")?; 157 | while let Some(client_conn) = endpoint.accept().await { 158 | let state = self.inner_state.clone(); 159 | let config = inner_state!(self, config).clone(); 160 | tokio::spawn(async move { 161 | let client_conn = client_conn.await?; 162 | let tun_type = Self::authenticate_connection(&config, client_conn).await?; 163 | 164 | match tun_type { 165 | TunnelType::TcpOut(info) => { 166 | TcpTunnel::process(&info.conn, info.upstream_addr, config.tcp_timeout_ms) 167 | .await; 168 | } 169 | 170 | TunnelType::UdpOut(info) => { 171 | UdpTunnel::process(&info.conn, info.upstream_addr, config.udp_timeout_ms) 172 | .await 173 | } 174 | 175 | TunnelType::TcpIn(mut info) => { 176 | state 177 | .lock() 178 | .unwrap() 179 | .tcp_sessions 180 | .push(ConnectedTcpInSession { 181 | conn: info.conn.clone(), 182 | sender: info.tcp_server.clone_tcp_sender(), 183 | }); 184 | 185 | TcpTunnel::start( 186 | false, 187 | &info.conn, 188 | &mut info.tcp_server, 189 | &mut None, 190 | config.tcp_timeout_ms, 191 | ) 192 | .await; 193 | info.tcp_server.shutdown().await.ok(); 194 | } 195 | 196 | TunnelType::UdpIn(info) => { 197 | state 198 | .lock() 199 | .unwrap() 200 | .udp_sessions 201 | .push(ConnectedUdpInSession { 202 | conn: info.conn.clone(), 203 | sender: info.udp_server.clone_udp_sender(), 204 | }); 205 | 206 | UdpTunnel::start(&info.conn, info.udp_server, None, config.udp_timeout_ms) 207 | .await 208 | .ok(); 209 | } 210 | } 211 | 212 | Ok::<(), anyhow::Error>(()) 213 | }); 214 | } 215 | info!("quit!"); 216 | 217 | Ok(()) 218 | } 219 | 220 | async fn authenticate_connection( 221 | config: &ServerConfig, 222 | conn: quinn::Connection, 223 | ) -> Result { 224 | let remote_addr = &conn.remote_address(); 225 | 226 | info!("authenticating connection, addr:{remote_addr}"); 227 | let (mut quic_send, mut quic_recv) = conn 228 | .accept_bi() 229 | .await 230 | .context(format!("login request not received in time: {remote_addr}"))?; 231 | 232 | info!("received bi_stream request: {remote_addr}"); 233 | match TunnelMessage::recv(&mut quic_recv).await? { 234 | TunnelMessage::ReqLogin(login_info) => { 235 | info!("received ReqLogin request: {remote_addr}"); 236 | 237 | Self::check_password(config.password.as_str(), login_info.password.as_str())?; 238 | 239 | let upstream_addr = 240 | Self::obtain_upstream_addr(&login_info, &config.default_tcp_upstream)?; 241 | 242 | let tunnel_type = match login_info.tunnel_config.mode { 243 | TunnelMode::Out => match login_info.tunnel_config.upstream.upstream_type { 244 | UpstreamType::Tcp => TunnelType::TcpOut(TcpTunnelOutInfo { 245 | conn, 246 | upstream_addr, 247 | }), 248 | 249 | UpstreamType::Udp => TunnelType::UdpOut(UdpTunnelOutInfo { 250 | conn, 251 | upstream_addr, 252 | }), 253 | }, 254 | 255 | TunnelMode::In => match login_info.tunnel_config.upstream.upstream_type { 256 | UpstreamType::Tcp => { 257 | let tcp_server = match TcpServer::bind_and_start(upstream_addr).await { 258 | Ok(tcp_server) => tcp_server, 259 | Err(e) => { 260 | TunnelMessage::send_failure( 261 | &mut quic_send, 262 | format!("udp server failed to bind at: {upstream_addr}"), 263 | ) 264 | .await?; 265 | log_and_bail!("tcp_IN login rejected: {e}"); 266 | } 267 | }; 268 | 269 | TunnelMessage::send(&mut quic_send, &TunnelMessage::RespSuccess) 270 | .await?; 271 | TunnelType::TcpIn(TcpTunnelInInfo { conn, tcp_server }) 272 | } 273 | 274 | UpstreamType::Udp => { 275 | let udp_server = match UdpServer::bind_and_start(upstream_addr).await { 276 | Ok(udp_server) => udp_server, 277 | Err(e) => { 278 | TunnelMessage::send_failure( 279 | &mut quic_send, 280 | format!("udp server failed to bind at: {upstream_addr}"), 281 | ) 282 | .await?; 283 | log_and_bail!("udp_IN login rejected: {e}"); 284 | } 285 | }; 286 | 287 | TunnelMessage::send(&mut quic_send, &TunnelMessage::RespSuccess) 288 | .await?; 289 | TunnelType::UdpIn(UdpTunnelInInfo { conn, udp_server }) 290 | } 291 | }, 292 | }; 293 | 294 | TunnelMessage::send(&mut quic_send, &TunnelMessage::RespSuccess).await?; 295 | info!("connection authenticated! addr: {remote_addr}"); 296 | Ok(tunnel_type) 297 | } 298 | 299 | _ => { 300 | log_and_bail!("received unepxected message"); 301 | } 302 | } 303 | } 304 | 305 | fn obtain_upstream_addr( 306 | login_info: &LoginInfo, 307 | default_upstream: &Option, 308 | ) -> Result { 309 | let tcfg = &login_info.tunnel_config; 310 | Ok(match tcfg.upstream.upstream_addr { 311 | None => { 312 | if tcfg.mode == TunnelMode::In { 313 | log_and_bail!("explicit port is required to start TunnelIn mode tunneling"); 314 | } 315 | 316 | if default_upstream.is_none() { 317 | log_and_bail!( 318 | "explicit {} upstream address must be specified when logging in because there's no default upstream specified for the server", 319 | tcfg.upstream.upstream_type 320 | ); 321 | } 322 | 323 | default_upstream.unwrap() 324 | } 325 | 326 | Some(addr) => { 327 | if tcfg.mode == TunnelMode::In 328 | && !addr.ip().is_unspecified() 329 | && !addr.ip().is_loopback() 330 | { 331 | log_and_bail!( 332 | "only loopback or unspecified IP is allowed for TunnelIn mode tunelling: {addr:?}, or simply specify a port without the IP part" 333 | ); 334 | } 335 | 336 | addr 337 | } 338 | }) 339 | } 340 | 341 | fn clear_expired_sessions(state: Arc>) { 342 | let mut state = state.lock().unwrap(); 343 | state.udp_sessions.retain(|sess| { 344 | if sess.conn.close_reason().is_some() { 345 | let sess = sess.clone(); 346 | tokio::spawn(async move { 347 | sess.sender.send(UdpMessage::Quit).await.ok(); 348 | debug!("dropped udp session: {}", sess.conn.remote_address()); 349 | }); 350 | false 351 | } else { 352 | true 353 | } 354 | }); 355 | 356 | state.tcp_sessions.retain(|sess| { 357 | if sess.conn.close_reason().is_some() { 358 | let sess = sess.clone(); 359 | tokio::spawn(async move { 360 | sess.sender.send(TcpMessage::Quit).await.ok(); 361 | debug!("dropped tcp session: {}", sess.conn.remote_address()); 362 | }); 363 | false 364 | } else { 365 | true 366 | } 367 | }); 368 | } 369 | 370 | fn read_certs_and_key( 371 | cert_path: &str, 372 | key_path: &str, 373 | ) -> Result<(Vec>, PrivateKeyDer<'static>)> { 374 | let (certs, key) = if cert_path.is_empty() { 375 | static ONCE: Once = Once::new(); 376 | ONCE.call_once(|| { 377 | info!("will use auto-generated self-signed certificate."); 378 | warn!("============================= WARNING =============================="); 379 | warn!("No valid certificate path is provided, a self-signed certificate"); 380 | warn!("for the domain \"localhost\" is generated."); 381 | warn!("============== Be cautious, this is for TEST only!!! ==============="); 382 | }); 383 | 384 | let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()])?; 385 | let key = PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der()); 386 | let cert = CertificateDer::from(cert.cert); 387 | (vec![cert], PrivateKeyDer::Pkcs8(key)) 388 | } else { 389 | let certs = pem_util::load_certificates_from_pem(cert_path) 390 | .context(format!("failed to read cert file: {cert_path}"))?; 391 | let key = pem_util::load_private_key_from_pem(key_path) 392 | .context(format!("failed to read key file: {key_path}"))?; 393 | (certs, key) 394 | }; 395 | 396 | Ok((certs, key)) 397 | } 398 | 399 | fn check_password(password1: &str, password2: &str) -> Result<()> { 400 | if password1 != password2 { 401 | log_and_bail!("passwords don't match!"); 402 | } 403 | Ok(()) 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/tcp/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod tcp_server; 2 | pub mod tcp_tunnel; 3 | -------------------------------------------------------------------------------- /src/tcp/tcp_server.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use log::{debug, error, info}; 3 | use std::net::SocketAddr; 4 | use std::sync::{Arc, Mutex}; 5 | use std::time::Duration; 6 | use tokio::net::{TcpListener, TcpStream}; 7 | use tokio::sync::mpsc::error::SendTimeoutError; 8 | use tokio::sync::mpsc::{channel, Receiver, Sender}; 9 | 10 | pub enum TcpMessage { 11 | Request(TcpStream), 12 | Quit, 13 | } 14 | 15 | pub type TcpSender = Sender; 16 | pub type TcpReceiver = Receiver; 17 | 18 | #[derive(Debug, Clone)] 19 | pub struct TcpServer { 20 | state: Arc>, 21 | } 22 | 23 | #[derive(Debug)] 24 | struct State { 25 | addr: SocketAddr, 26 | tcp_sender: TcpSender, 27 | tcp_receiver: Option, 28 | active: bool, 29 | terminated: bool, 30 | } 31 | 32 | impl TcpServer { 33 | pub async fn bind_and_start(addr: SocketAddr) -> Result { 34 | let tcp_listener = TcpListener::bind(addr).await?; 35 | let addr = tcp_listener.local_addr().unwrap(); 36 | 37 | let (tcp_sender, tcp_receiver) = channel(4); 38 | let state = Arc::new(Mutex::new(State { 39 | addr, 40 | tcp_sender: tcp_sender.clone(), 41 | tcp_receiver: Some(tcp_receiver), 42 | active: false, 43 | terminated: false, 44 | })); 45 | let state_clone = state.clone(); 46 | 47 | tokio::spawn(async move { 48 | loop { 49 | match tcp_listener.accept().await { 50 | Ok((socket, addr)) => { 51 | { 52 | let (terminated, active) = { 53 | let state = state.lock().unwrap(); 54 | (state.terminated, state.active) 55 | }; 56 | 57 | if terminated { 58 | tcp_sender.send(TcpMessage::Quit).await.ok(); 59 | break; 60 | } 61 | 62 | if !active { 63 | // unless being explicitly requested, always drop the connections because we are not 64 | // sure whether the receiver is ready to aceept connections 65 | debug!("drop connection: {addr}"); 66 | continue; 67 | } 68 | } 69 | 70 | match tcp_sender 71 | .send_timeout(TcpMessage::Request(socket), Duration::from_millis(3000)) 72 | .await 73 | { 74 | Ok(_) => { 75 | // succeeded 76 | } 77 | Err(SendTimeoutError::Timeout(_)) => { 78 | debug!("timedout sending the request, drop the socket"); 79 | } 80 | Err(e) => { 81 | info!("channel is closed, will quit tcp server, err: {e}"); 82 | break; 83 | } 84 | } 85 | } 86 | 87 | Err(e) => { 88 | error!("tcp server failed, err: {e}"); 89 | } 90 | } 91 | } 92 | info!("tcp server quit: {addr}"); 93 | }); 94 | 95 | Ok(Self { state: state_clone }) 96 | } 97 | 98 | pub async fn shutdown(&mut self) -> Result<()> { 99 | let addr = { 100 | let mut state = self.state.lock().unwrap(); 101 | state.terminated = true; 102 | state.addr 103 | }; 104 | // initiate a new connection to wake up the accept() loop 105 | TcpStream::connect(addr).await?; 106 | Ok(()) 107 | } 108 | 109 | pub fn addr(&self) -> SocketAddr { 110 | self.state.lock().unwrap().addr 111 | } 112 | 113 | pub fn take_tcp_receiver(&mut self) -> Option { 114 | self.state.lock().unwrap().tcp_receiver.take() 115 | } 116 | 117 | pub fn put_tcp_receiver(&mut self, tcp_receiver: TcpReceiver) { 118 | self.state.lock().unwrap().tcp_receiver = Some(tcp_receiver) 119 | } 120 | 121 | pub fn clone_tcp_sender(&self) -> Sender { 122 | self.state.lock().unwrap().tcp_sender.clone() 123 | } 124 | 125 | pub fn set_active(&self, flag: bool) { 126 | self.state.lock().unwrap().active = flag 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/tcp/tcp_tunnel.rs: -------------------------------------------------------------------------------- 1 | use super::tcp_server::TcpMessage; 2 | use crate::{TcpServer, BUFFER_POOL}; 3 | use anyhow::Result; 4 | use log::{debug, error, info}; 5 | use quinn::{RecvStream, SendStream}; 6 | use std::borrow::BorrowMut; 7 | use std::net::SocketAddr; 8 | use std::sync::atomic::{AtomicI32, Ordering}; 9 | use std::sync::Arc; 10 | use std::time::Duration; 11 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 12 | use tokio::net::tcp::OwnedReadHalf; 13 | use tokio::net::tcp::OwnedWriteHalf; 14 | use tokio::net::TcpStream; 15 | use tokio::time::error::Elapsed; 16 | 17 | #[derive(Debug, PartialEq, Eq)] 18 | pub enum TransferError { 19 | InternalError, 20 | TimeoutError, 21 | } 22 | 23 | pub struct TcpTunnel; 24 | 25 | impl TcpTunnel { 26 | pub async fn start( 27 | tunnel_out: bool, 28 | conn: &quinn::Connection, 29 | tcp_server: &mut TcpServer, 30 | pending_stream: &mut Option, 31 | tcp_timeout_ms: u64, 32 | ) { 33 | tcp_server.set_active(true); 34 | let mut tcp_receiver = tcp_server.take_tcp_receiver().unwrap(); 35 | loop { 36 | let tcp_stream = match pending_stream.take() { 37 | Some(tcp_stream) => tcp_stream, 38 | None => match tcp_receiver.borrow_mut().recv().await { 39 | Some(TcpMessage::Request(tcp_stream)) => tcp_stream, 40 | _ => break, 41 | }, 42 | }; 43 | 44 | match conn.open_bi().await { 45 | Ok(quic_stream) => { 46 | TcpTunnel::run(tunnel_out, tcp_stream, quic_stream, tcp_timeout_ms) 47 | } 48 | Err(e) => { 49 | error!("failed to open_bi, will retry: {e}"); 50 | *pending_stream = Some(tcp_stream); 51 | break; 52 | } 53 | } 54 | } 55 | tcp_server.put_tcp_receiver(tcp_receiver); 56 | // the tcp server will be reused when tunnel reconnects 57 | tcp_server.set_active(false); 58 | } 59 | 60 | fn run( 61 | tunnel_out: bool, 62 | tcp_stream: TcpStream, 63 | quic_stream: (SendStream, RecvStream), 64 | tcp_timeout_ms: u64, 65 | ) { 66 | let (mut tcp_read, mut tcp_write) = tcp_stream.into_split(); 67 | let (mut quic_send, mut quic_recv) = quic_stream; 68 | 69 | let tag = if tunnel_out { "OUT" } else { "IN" }; 70 | let index = quic_send.id().index(); 71 | let in_addr = match tcp_read.peer_addr() { 72 | Ok(in_addr) => in_addr, 73 | Err(e) => { 74 | log::error!("failed to get peer_addr: {e:?}"); 75 | return; 76 | } 77 | }; 78 | 79 | debug!("[{tag}] START {index:<3} → {in_addr:<20}"); 80 | 81 | let loop_count = Arc::new(AtomicI32::new(0)); 82 | let loop_count_clone = loop_count.clone(); 83 | const BUFFER_SIZE: usize = 8192; 84 | 85 | tokio::spawn(async move { 86 | let mut transfer_bytes = 0u64; 87 | let mut buffer = BUFFER_POOL.alloc_and_fill(BUFFER_SIZE); 88 | loop { 89 | let c_start = loop_count.load(Ordering::Relaxed); 90 | let result = Self::quic_to_tcp( 91 | &mut quic_recv, 92 | &mut tcp_write, 93 | &mut buffer, 94 | &mut transfer_bytes, 95 | tcp_timeout_ms, 96 | ) 97 | .await; 98 | let c_end = loop_count.fetch_add(1, Ordering::Relaxed); 99 | 100 | match result { 101 | Err(TransferError::TimeoutError) => { 102 | if c_start == c_end { 103 | log::warn!("quic to tcp timeout"); 104 | break; 105 | } 106 | } 107 | Ok(0) | Err(_) => { 108 | break; 109 | } 110 | _ => { 111 | // ok 112 | } 113 | } 114 | } 115 | 116 | debug!("[{tag}] END {index:<3} → {in_addr}, {transfer_bytes} bytes"); 117 | }); 118 | 119 | tokio::spawn(async move { 120 | let mut transfer_bytes = 0u64; 121 | let mut buffer = BUFFER_POOL.alloc_and_fill(BUFFER_SIZE); 122 | loop { 123 | let c_start = loop_count_clone.load(Ordering::Relaxed); 124 | let result = Self::tcp_to_quic( 125 | &mut tcp_read, 126 | &mut quic_send, 127 | &mut buffer, 128 | &mut transfer_bytes, 129 | tcp_timeout_ms, 130 | ) 131 | .await; 132 | let c_end = loop_count_clone.fetch_add(1, Ordering::Relaxed); 133 | 134 | match result { 135 | Err(TransferError::TimeoutError) => { 136 | if c_start == c_end { 137 | log::warn!("tcp to quic timeout"); 138 | break; 139 | } 140 | } 141 | Ok(0) | Err(_) => { 142 | break; 143 | } 144 | _ => { 145 | // ok 146 | } 147 | } 148 | } 149 | 150 | debug!("[{tag}] END {index:<3} ← {in_addr}, {transfer_bytes} bytes"); 151 | }); 152 | } 153 | 154 | async fn tcp_to_quic( 155 | tcp_read: &mut OwnedReadHalf, 156 | quic_send: &mut SendStream, 157 | buffer: &mut [u8], 158 | transfer_bytes: &mut u64, 159 | tcp_timeout_ms: u64, 160 | ) -> Result { 161 | let len_read = 162 | tokio::time::timeout(Duration::from_millis(tcp_timeout_ms), tcp_read.read(buffer)) 163 | .await 164 | .map_err(|_: Elapsed| TransferError::TimeoutError)? 165 | .map_err(|_| TransferError::InternalError)?; 166 | if len_read > 0 { 167 | *transfer_bytes += len_read as u64; 168 | quic_send 169 | .write_all(&buffer[..len_read]) 170 | .await 171 | .map_err(|_| TransferError::InternalError)?; 172 | Ok(len_read) 173 | } else { 174 | quic_send 175 | .finish() 176 | .map_err(|_| TransferError::InternalError)?; 177 | Ok(0) 178 | } 179 | } 180 | 181 | async fn quic_to_tcp( 182 | quic_recv: &mut RecvStream, 183 | tcp_write: &mut OwnedWriteHalf, 184 | buffer: &mut [u8], 185 | transfer_bytes: &mut u64, 186 | tcp_timeout_ms: u64, 187 | ) -> Result { 188 | let result = tokio::time::timeout( 189 | Duration::from_millis(tcp_timeout_ms), 190 | quic_recv.read(buffer), 191 | ) 192 | .await 193 | .map_err(|_: Elapsed| TransferError::TimeoutError)? 194 | .map_err(|_| TransferError::InternalError)?; 195 | if let Some(len_read) = result { 196 | *transfer_bytes += len_read as u64; 197 | tcp_write 198 | .write_all(&buffer[..len_read]) 199 | .await 200 | .map_err(|_| TransferError::InternalError)?; 201 | Ok(len_read) 202 | } else { 203 | tcp_write 204 | .shutdown() 205 | .await 206 | .map_err(|_| TransferError::InternalError)?; 207 | Ok(0) 208 | } 209 | } 210 | 211 | pub async fn process(conn: &quinn::Connection, upstream_addr: SocketAddr, tcp_timeout_ms: u64) { 212 | let remote_addr = &conn.remote_address(); 213 | info!("start tcp streaming, {remote_addr} ↔ {upstream_addr}"); 214 | 215 | loop { 216 | match conn.accept_bi().await { 217 | Err(quinn::ConnectionError::TimedOut { .. }) => { 218 | info!("connection timeout: {remote_addr}"); 219 | break; 220 | } 221 | Err(quinn::ConnectionError::ApplicationClosed { .. }) => { 222 | debug!("connection closed: {remote_addr}"); 223 | break; 224 | } 225 | Err(e) => { 226 | error!("failed to open accpet_bi: {remote_addr}, err: {e}"); 227 | break; 228 | } 229 | Ok(quic_stream) => tokio::spawn(async move { 230 | match tokio::time::timeout( 231 | Duration::from_secs(5), 232 | TcpStream::connect(&upstream_addr), 233 | ) 234 | .await 235 | { 236 | Ok(Ok(tcp_stream)) => { 237 | Self::run(true, tcp_stream, quic_stream, tcp_timeout_ms) 238 | } 239 | Ok(Err(e)) => error!("failed to connect to {upstream_addr}, err: {e}"), 240 | Err(_) => error!("timeout connecting to {upstream_addr}"), 241 | } 242 | }), 243 | }; 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/tunnel_info_bridge.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | #[derive(Serialize, Default, Clone)] 5 | pub(crate) struct TunnelTraffic { 6 | pub rx_bytes: u64, 7 | pub tx_bytes: u64, 8 | pub tx_dgrams: u64, 9 | pub rx_dgrams: u64, 10 | } 11 | 12 | #[derive(Serialize)] 13 | pub(crate) enum TunnelInfoType { 14 | TunnelState, 15 | TunnelLog, 16 | TunnelTraffic, 17 | } 18 | 19 | #[derive(Serialize)] 20 | pub(crate) struct TunnelInfo 21 | where 22 | T: ?Sized + Serialize, 23 | { 24 | pub info_type: TunnelInfoType, 25 | pub data: Box, 26 | } 27 | 28 | impl TunnelInfo 29 | where 30 | T: ?Sized + Serialize, 31 | { 32 | pub(crate) fn new(info_type: TunnelInfoType, data: Box) -> Self { 33 | Self { info_type, data } 34 | } 35 | } 36 | 37 | #[derive(Clone)] 38 | pub(crate) struct TunnelInfoBridge { 39 | listener: Option>>, 40 | } 41 | 42 | impl TunnelInfoBridge { 43 | pub(crate) fn new() -> Self { 44 | TunnelInfoBridge { listener: None } 45 | } 46 | 47 | pub(crate) fn set_listener(&mut self, listener: impl FnMut(&str) + 'static + Send + Sync) { 48 | self.listener = Some(Arc::new(Mutex::new(listener))); 49 | } 50 | 51 | pub(crate) fn has_listener(&self) -> bool { 52 | self.listener.is_some() 53 | } 54 | 55 | pub(crate) fn post_tunnel_info(&self, data: TunnelInfo) 56 | where 57 | T: ?Sized + Serialize, 58 | { 59 | if let Some(ref listener) = self.listener { 60 | if let Ok(json) = serde_json::to_string(&data) { 61 | listener.lock().unwrap()(json.as_str()); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/tunnel_message.rs: -------------------------------------------------------------------------------- 1 | use crate::{TunnelConfig, TunnelMode, UpstreamType}; 2 | use anyhow::Result; 3 | use anyhow::{bail, Context}; 4 | use bincode::config::{self, Configuration}; 5 | use enum_as_inner::EnumAsInner; 6 | use quinn::{RecvStream, SendStream}; 7 | use serde::{Deserialize, Serialize}; 8 | use std::fmt::Display; 9 | use std::net::SocketAddr; 10 | use std::time::Duration; 11 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 12 | 13 | #[derive(EnumAsInner, Serialize, Deserialize, Debug, Clone)] 14 | pub enum TunnelMessage { 15 | ReqLogin(LoginInfo), 16 | ReqUdpStart(UdpLocalAddr), 17 | RespFailure(String), 18 | RespSuccess, 19 | } 20 | 21 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 22 | pub struct LoginInfo { 23 | pub password: String, 24 | pub tunnel_config: TunnelConfig, 25 | } 26 | 27 | impl LoginInfo { 28 | pub fn format_with_remote_addr(&self, remote_addr: &SocketAddr) -> String { 29 | let cfg = &self.tunnel_config; 30 | let upstream = &cfg.upstream; 31 | let upstream_str = if let Some(upstream) = upstream.upstream_addr { 32 | if upstream.ip().is_loopback() { 33 | format!("{}:{}", remote_addr.ip(), upstream.port()) 34 | } else { 35 | format!("{upstream}") 36 | } 37 | } else { 38 | String::from("PeerDefault") 39 | }; 40 | 41 | match self.tunnel_config.mode { 42 | TunnelMode::Out => { 43 | format!( 44 | "{}_OUT → {} → {remote_addr} → {upstream_str}", 45 | if upstream.upstream_type == UpstreamType::Tcp { 46 | "TCP" 47 | } else { 48 | "UDP" 49 | }, 50 | cfg.local_server_addr.unwrap() 51 | ) 52 | } 53 | TunnelMode::In => { 54 | format!( 55 | "{}_IN ← {} ← {remote_addr} ← {upstream_str}", 56 | if upstream.upstream_type == UpstreamType::Tcp { 57 | "TCP" 58 | } else { 59 | "UDP" 60 | }, 61 | cfg.local_server_addr.unwrap() 62 | ) 63 | } 64 | } 65 | } 66 | } 67 | 68 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] 69 | pub struct UdpLocalAddr(pub SocketAddr); 70 | 71 | impl Display for LoginInfo { 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 73 | f.write_str( 74 | format!( 75 | "{}_{}", 76 | self.tunnel_config.upstream.upstream_type, self.tunnel_config.mode 77 | ) 78 | .as_str(), 79 | ) 80 | } 81 | } 82 | 83 | impl Display for TunnelMessage { 84 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 85 | match self { 86 | Self::ReqLogin(login_info) => f.write_str(login_info.to_string().as_str()), 87 | Self::ReqUdpStart(udp_local_addr) => { 88 | f.write_str(format!("udp_start:{udp_local_addr:?}").as_str()) 89 | } 90 | Self::RespFailure(msg) => f.write_str(format!("fail:{msg}").as_str()), 91 | Self::RespSuccess => f.write_str("succeeded"), 92 | } 93 | } 94 | } 95 | 96 | impl TunnelMessage { 97 | pub async fn recv(quic_recv: &mut RecvStream) -> Result { 98 | let msg_len = quic_recv.read_u32().await? as usize; 99 | let mut msg = vec![0; msg_len]; 100 | quic_recv 101 | .read_exact(&mut msg) 102 | .await 103 | .context("read message failed")?; 104 | 105 | let tun_msg = bincode::serde::decode_from_slice::( 106 | &msg, 107 | config::standard(), 108 | ) 109 | .context("deserialize message failed")?; 110 | Ok(tun_msg.0) 111 | } 112 | 113 | pub async fn send(quic_send: &mut SendStream, msg: &TunnelMessage) -> Result<()> { 114 | let msg = bincode::serde::encode_to_vec(msg, config::standard()) 115 | .context("serialize message failed")?; 116 | quic_send.write_u32(msg.len() as u32).await?; 117 | quic_send.write_all(&msg).await?; 118 | Ok(()) 119 | } 120 | 121 | pub async fn send_failure(quic_send: &mut SendStream, msg: String) -> Result<()> { 122 | let msg = TunnelMessage::RespFailure(msg); 123 | Self::send(quic_send, &msg).await?; 124 | quic_send.flush().await?; 125 | tokio::time::sleep(Duration::from_millis(200)).await; 126 | Ok(()) 127 | } 128 | 129 | pub async fn recv_raw(quic_recv: &mut RecvStream, data: &mut [u8]) -> Result { 130 | let msg_len = quic_recv.read_u16().await? as usize; 131 | if msg_len > data.len() { 132 | bail!("message too large: {msg_len}"); 133 | } 134 | quic_recv 135 | .read_exact(&mut data[..msg_len]) 136 | .await 137 | .context("read message failed")?; 138 | Ok(msg_len as u16) 139 | } 140 | 141 | pub async fn send_raw(quic_send: &mut SendStream, data: &[u8]) -> Result<()> { 142 | quic_send.write_u16(data.len() as u16).await?; 143 | quic_send.write_all(data).await?; 144 | Ok(()) 145 | } 146 | 147 | pub fn handle_message(msg: &TunnelMessage) -> Result<()> { 148 | match msg { 149 | TunnelMessage::RespSuccess => Ok(()), 150 | TunnelMessage::RespFailure(msg) => bail!(format!("received failure, err: {msg}")), 151 | _ => bail!("unexpected message type"), 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/udp/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod udp_packet; 2 | pub mod udp_server; 3 | pub mod udp_tunnel; 4 | -------------------------------------------------------------------------------- /src/udp/udp_packet.rs: -------------------------------------------------------------------------------- 1 | use crate::{PooledBuffer, BUFFER_POOL, UDP_PACKET_SIZE}; 2 | use anyhow::{bail, Result}; 3 | use byte_pool::Block; 4 | use bytes::{BufMut, Bytes, BytesMut}; 5 | use log::error; 6 | use rs_utilities::log_and_bail; 7 | use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; 8 | 9 | pub struct UdpPacket { 10 | pub payload: Block<'static, Vec>, 11 | pub addr: SocketAddr, 12 | } 13 | 14 | impl UdpPacket { 15 | pub fn new(payload: PooledBuffer, addr: SocketAddr) -> Self { 16 | Self { payload, addr } 17 | } 18 | 19 | pub fn serialize(&self) -> Bytes { 20 | let mut buf = BytesMut::new(); 21 | 22 | match &self.addr { 23 | SocketAddr::V4(addr_v4) => { 24 | buf.put_u8(4); 25 | buf.extend_from_slice(&addr_v4.ip().octets()); 26 | self.append_port_and_payload(&mut buf, addr_v4.port()); 27 | } 28 | SocketAddr::V6(addr_v6) => { 29 | buf.put_u8(6); 30 | buf.extend_from_slice(&addr_v6.ip().octets()); 31 | self.append_port_and_payload(&mut buf, addr_v6.port()); 32 | } 33 | } 34 | 35 | buf.freeze() 36 | } 37 | 38 | fn append_port_and_payload(&self, buf: &mut BytesMut, port: u16) { 39 | buf.extend_from_slice(&port.to_be_bytes()); // 2-byte port in big-endian 40 | let payload_len = self.payload.len() as u16; 41 | buf.extend_from_slice(&payload_len.to_be_bytes()); // 2-byte payload length 42 | buf.extend_from_slice(&self.payload); // Payload data 43 | } 44 | 45 | pub fn deserialize(data: &[u8]) -> Result { 46 | let version = data[0]; // First byte is the version (4 or 6) 47 | let mut offset = 1; 48 | 49 | let addr = match version { 50 | 4 => { 51 | let ip_bytes: [u8; 4] = data[offset..offset + 4].try_into()?; 52 | offset += 4; 53 | let port = u16::from_be_bytes(data[offset..offset + 2].try_into()?); 54 | SocketAddr::V4(SocketAddrV4::new(ip_bytes.into(), port)) 55 | } 56 | 6 => { 57 | let ip_bytes: [u8; 16] = data[offset..offset + 16].try_into()?; 58 | offset += 16; 59 | let port = u16::from_be_bytes(data[offset..offset + 2].try_into()?); 60 | SocketAddr::V6(SocketAddrV6::new(ip_bytes.into(), port, 0, 0)) 61 | } 62 | _ => { 63 | log_and_bail!("invalid version"); 64 | } 65 | }; 66 | 67 | offset += 2; // Move past the port 68 | let payload_len = u16::from_be_bytes(data[offset..offset + 2].try_into()?) as usize; 69 | offset += 2; 70 | let remaining_len = data.len() - offset; 71 | if payload_len != remaining_len { 72 | log_and_bail!( 73 | "unexpected packet length, payload_len:{payload_len} != remaining:{remaining_len}" 74 | ); 75 | } 76 | 77 | let mut payload = BUFFER_POOL.alloc(UDP_PACKET_SIZE); 78 | payload.extend_from_slice(&data[offset..offset + payload_len]); 79 | Ok(UdpPacket { payload, addr }) 80 | } 81 | } 82 | 83 | impl From for Bytes { 84 | fn from(val: UdpPacket) -> Self { 85 | val.serialize() 86 | } 87 | } 88 | 89 | impl TryFrom for UdpPacket { 90 | type Error = anyhow::Error; 91 | 92 | fn try_from(data: Bytes) -> std::result::Result { 93 | UdpPacket::deserialize(&data) 94 | } 95 | } 96 | 97 | impl TryFrom<&[u8]> for UdpPacket { 98 | type Error = anyhow::Error; 99 | 100 | fn try_from(data: &[u8]) -> std::result::Result { 101 | UdpPacket::deserialize(data) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/udp/udp_server.rs: -------------------------------------------------------------------------------- 1 | use super::udp_packet::UdpPacket; 2 | use crate::BUFFER_POOL; 3 | use crate::UDP_PACKET_SIZE; 4 | use anyhow::Result; 5 | use log::debug; 6 | use log::error; 7 | use log::info; 8 | use std::net::SocketAddr; 9 | use std::sync::Arc; 10 | use std::sync::Mutex; 11 | use std::time::Duration; 12 | use tokio::net::UdpSocket; 13 | use tokio::sync::mpsc::channel; 14 | use tokio::sync::mpsc::Receiver; 15 | use tokio::sync::mpsc::Sender; 16 | 17 | pub enum UdpMessage { 18 | Packet(UdpPacket), 19 | Quit, 20 | } 21 | 22 | pub type UdpSender = Sender; 23 | pub type UdpReceiver = Receiver; 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct UdpServer(Arc>); 27 | 28 | #[derive(Debug)] 29 | struct State { 30 | addr: SocketAddr, 31 | active: bool, 32 | in_udp_sender: UdpSender, 33 | udp_receiver: Option, 34 | } 35 | 36 | impl UdpServer { 37 | pub async fn bind_and_start(addr: SocketAddr) -> Result { 38 | let udp_socket = UdpSocket::bind(addr).await?; 39 | let addr = udp_socket.local_addr().unwrap(); 40 | 41 | let (in_udp_sender, mut in_udp_receiver) = channel::(4); 42 | let (out_udp_sender, out_udp_receiver) = channel::(4); 43 | 44 | let state = Arc::new(Mutex::new(State { 45 | addr, 46 | active: false, 47 | in_udp_sender, 48 | udp_receiver: Some(out_udp_receiver), 49 | })); 50 | let state_clone = state.clone(); 51 | 52 | tokio::spawn(async move { 53 | loop { 54 | let mut buf = BUFFER_POOL.alloc_and_fill(UDP_PACKET_SIZE); 55 | tokio::select! { 56 | result = udp_socket.recv_from(&mut buf) => { 57 | match result { 58 | Ok((size, addr)) => { 59 | let active = { 60 | state.clone().lock().unwrap().active 61 | }; 62 | if !active { 63 | debug!("drop the packet ({size}) from addr: {addr}"); 64 | continue; 65 | } 66 | 67 | unsafe { buf.set_len(size); } 68 | let msg = UdpMessage::Packet(UdpPacket::new(buf, addr)); 69 | match tokio::time::timeout( 70 | Duration::from_millis(300), 71 | out_udp_sender.send(msg)).await { 72 | Ok(Ok(_)) => { 73 | // succeeded 74 | } 75 | Err(_) => { 76 | // timeout 77 | } 78 | Ok(Err(e)) => { 79 | error!("receiving end of the channel is closed, will quit. err: {e}"); 80 | break; 81 | } 82 | } 83 | } 84 | Err(e) => { 85 | error!("failed to read from local udp socket, err: {e}"); 86 | } 87 | } 88 | } 89 | 90 | result = in_udp_receiver.recv() => { 91 | match result { 92 | Some(UdpMessage::Packet(p)) => { 93 | match udp_socket.send_to(&p.payload, p.addr).await { 94 | Ok(_) => { 95 | // succeeded 96 | } 97 | Err(e) => { 98 | error!("failed to send packet to local, err: {e}"); 99 | } 100 | } 101 | } 102 | Some(UdpMessage::Quit) => { 103 | info!("udp server is requested to quit"); 104 | break; 105 | } 106 | None => { 107 | // all senders quit 108 | info!("udp server quit"); 109 | break; 110 | } 111 | } 112 | } 113 | } 114 | } 115 | 116 | info!("udp server quit: {addr}"); 117 | }); 118 | 119 | Ok(Self(state_clone)) 120 | } 121 | 122 | pub fn addr(&self) -> SocketAddr { 123 | self.0.lock().unwrap().addr 124 | } 125 | 126 | pub async fn shutdown(&mut self) -> Result<()> { 127 | let udp_sender = self.0.lock().unwrap().in_udp_sender.clone(); 128 | udp_sender.send(UdpMessage::Quit).await?; 129 | Ok(()) 130 | } 131 | 132 | pub fn set_active(&mut self, active: bool) { 133 | self.0.lock().unwrap().active = active 134 | } 135 | 136 | pub fn take_receiver(&mut self) -> Option { 137 | self.0.lock().unwrap().udp_receiver.take() 138 | } 139 | 140 | pub fn put_receiver(&mut self, udp_receiver: UdpReceiver) { 141 | self.0.lock().unwrap().udp_receiver = Some(udp_receiver); 142 | } 143 | 144 | pub fn clone_udp_sender(&self) -> UdpSender { 145 | self.0.lock().unwrap().in_udp_sender.clone() 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/udp/udp_tunnel.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | tcp::tcp_server::{TcpMessage, TcpSender}, 3 | tunnel_message::{TunnelMessage, UdpLocalAddr}, 4 | udp::{udp_packet::UdpPacket, udp_server::UdpMessage}, 5 | BUFFER_POOL, UDP_PACKET_SIZE, 6 | }; 7 | 8 | use super::udp_server::UdpServer; 9 | use anyhow::{bail, Context, Result}; 10 | use dashmap::DashMap; 11 | use log::{debug, error, info, warn}; 12 | use quinn::{Connection, RecvStream, SendStream}; 13 | use rs_utilities::log_and_bail; 14 | use std::{ 15 | net::{IpAddr, Ipv4Addr, SocketAddr}, 16 | sync::Arc, 17 | time::Duration, 18 | }; 19 | use tokio::net::UdpSocket; 20 | 21 | type TSafe = Arc>; 22 | 23 | pub struct UdpTunnel; 24 | 25 | impl UdpTunnel { 26 | pub async fn start( 27 | conn: &quinn::Connection, 28 | mut udp_server: UdpServer, 29 | tcp_sender: Option, 30 | udp_timeout_ms: u64, 31 | ) -> Result<()> { 32 | let stream_map = Arc::new(DashMap::new()); 33 | udp_server.set_active(true); 34 | let mut udp_receiver = udp_server.take_receiver().unwrap(); 35 | 36 | debug!("start transfering udp packets from: {}", udp_server.addr()); 37 | while let Some(UdpMessage::Packet(packet)) = udp_receiver.recv().await { 38 | let quic_send = match UdpTunnel::open_stream( 39 | conn.clone(), 40 | udp_server.clone(), 41 | packet.addr, 42 | stream_map.clone(), 43 | udp_timeout_ms, 44 | ) 45 | .await 46 | { 47 | Ok(quic_send) => quic_send, 48 | Err(e) => { 49 | error!("{e}"); 50 | if conn.close_reason().is_some() { 51 | if let Some(tcp_sender) = tcp_sender { 52 | tcp_sender.send(TcpMessage::Quit).await.ok(); 53 | } 54 | debug!("connection is closed, will quit"); 55 | break; 56 | } 57 | continue; 58 | } 59 | }; 60 | 61 | // send the packet using an async task 62 | tokio::spawn(async move { 63 | let mut quic_send = quic_send.lock().await; 64 | let payload_len = packet.payload.len(); 65 | TunnelMessage::send_raw(&mut quic_send, &packet.payload) 66 | .await 67 | .inspect_err(|e| { 68 | warn!( 69 | "failed to send datagram({payload_len}) through the tunnel, err: {e}" 70 | ); 71 | }) 72 | .ok(); 73 | }); 74 | } 75 | 76 | // put the receiver back 77 | udp_server.set_active(false); 78 | udp_server.put_receiver(udp_receiver); 79 | info!("local udp server paused"); 80 | Ok(()) 81 | } 82 | 83 | async fn open_stream( 84 | conn: Connection, 85 | udp_server: UdpServer, 86 | peer_addr: SocketAddr, 87 | stream_map: Arc>>, 88 | udp_timeout_ms: u64, 89 | ) -> Result> { 90 | if let Some(s) = stream_map.get(&peer_addr) { 91 | return Ok((*s).clone()); 92 | } 93 | 94 | let (mut quic_send, mut quic_recv) = 95 | conn.open_bi().await.context("open_bi failed for udp out")?; 96 | 97 | TunnelMessage::send( 98 | &mut quic_send, 99 | &TunnelMessage::ReqUdpStart(UdpLocalAddr(peer_addr)), 100 | ) 101 | .await?; 102 | 103 | debug!( 104 | "new udp session: {peer_addr}, streams: {}", 105 | stream_map.len() 106 | ); 107 | 108 | let quic_send = Arc::new(tokio::sync::Mutex::new(quic_send)); 109 | stream_map.insert(peer_addr, quic_send.clone()); 110 | let udp_sender = udp_server.clone_udp_sender(); 111 | 112 | let stream_map = stream_map.clone(); 113 | tokio::spawn(async move { 114 | debug!( 115 | "start udp stream: {peer_addr}, streams: {}", 116 | stream_map.len() 117 | ); 118 | loop { 119 | let mut buf = BUFFER_POOL.alloc_and_fill(UDP_PACKET_SIZE); 120 | match tokio::time::timeout( 121 | Duration::from_millis(udp_timeout_ms), 122 | TunnelMessage::recv_raw(&mut quic_recv, &mut buf), 123 | ) 124 | .await 125 | { 126 | Ok(Ok(len)) => { 127 | unsafe { 128 | buf.set_len(len as usize); 129 | } 130 | let packet = UdpPacket { 131 | payload: buf, 132 | addr: peer_addr, 133 | }; 134 | udp_sender.send(UdpMessage::Packet(packet)).await.ok(); 135 | } 136 | e => { 137 | match e { 138 | Ok(Err(e)) => { 139 | warn!("failed to read for udp, err: {e}"); 140 | } 141 | Err(_) => { 142 | // timedout 143 | // debug!("timeout on reading udp packet"); 144 | } 145 | _ => unreachable!(""), 146 | } 147 | break; 148 | } 149 | } 150 | } 151 | 152 | stream_map.remove(&peer_addr); 153 | debug!( 154 | "drop udp session: {peer_addr}, streams: {}", 155 | stream_map.len() 156 | ); 157 | }); 158 | 159 | Ok(quic_send) 160 | } 161 | 162 | pub async fn process(conn: &quinn::Connection, upstream_addr: SocketAddr, udp_timeout_ms: u64) { 163 | let remote_addr = &conn.remote_address(); 164 | info!("start udp streaming, {remote_addr} ↔ {upstream_addr}"); 165 | 166 | loop { 167 | match conn.accept_bi().await { 168 | Err(quinn::ConnectionError::TimedOut { .. }) => { 169 | info!("connection timeout: {remote_addr}"); 170 | break; 171 | } 172 | Err(quinn::ConnectionError::ApplicationClosed { .. }) => { 173 | debug!("connection closed: {remote_addr}"); 174 | break; 175 | } 176 | Err(e) => { 177 | error!("failed to accpet_bi: {remote_addr}, err: {e}"); 178 | break; 179 | } 180 | Ok((quic_send, quic_recv)) => tokio::spawn(async move { 181 | Self::process_internal(quic_send, quic_recv, upstream_addr, udp_timeout_ms) 182 | .await 183 | }), 184 | }; 185 | } 186 | 187 | info!("connection for udp out is dropped"); 188 | } 189 | 190 | async fn process_internal( 191 | mut quic_send: SendStream, 192 | mut quic_recv: RecvStream, 193 | upstream_addr: SocketAddr, 194 | udp_timeout_ms: u64, 195 | ) -> Result<()> { 196 | let peer_addr = match TunnelMessage::recv(&mut quic_recv).await { 197 | Ok(TunnelMessage::ReqUdpStart(peer_addr)) => peer_addr.0, 198 | _ => { 199 | log_and_bail!("unexpected first udp message"); 200 | } 201 | }; 202 | debug!("new udp session: {peer_addr}"); 203 | 204 | let local_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); 205 | let udp_socket = match UdpSocket::bind(local_addr).await { 206 | Ok(udp_socket) => Arc::new(udp_socket), 207 | Err(e) => { 208 | log_and_bail!("failed to bind to localhost, err: {e:?}"); 209 | } 210 | }; 211 | 212 | if let Err(e) = udp_socket.connect(upstream_addr).await { 213 | log_and_bail!("failed to connect to upstream: {upstream_addr}, err: {e:?}"); 214 | }; 215 | 216 | let udp_socket_clone = udp_socket.clone(); 217 | tokio::spawn(async move { 218 | debug!("start udp stream: {peer_addr} ← {upstream_addr}"); 219 | let mut buf = BUFFER_POOL.alloc_and_fill(UDP_PACKET_SIZE); 220 | loop { 221 | match tokio::time::timeout( 222 | Duration::from_millis(udp_timeout_ms), 223 | udp_socket_clone.recv(&mut buf), 224 | ) 225 | .await 226 | { 227 | Ok(Ok(len)) => { 228 | TunnelMessage::send_raw(&mut quic_send, &buf[..len]) 229 | .await 230 | .ok(); 231 | } 232 | e => { 233 | match e { 234 | Ok(Err(e)) => { 235 | warn!("failed to receive datagrams from upstream: {upstream_addr}, err: {e:?}"); 236 | } 237 | Err(_) => { 238 | debug!( 239 | "timeout on receiving datagrams from upstream: {upstream_addr}" 240 | ); 241 | } 242 | _ => unreachable!(""), 243 | } 244 | break; 245 | } 246 | } 247 | } 248 | debug!("drop udp stream: {peer_addr} ← {upstream_addr}"); 249 | }); 250 | 251 | debug!("start sending datagrams to upstream, {peer_addr} → {upstream_addr}"); 252 | let mut buf = BUFFER_POOL.alloc_and_fill(UDP_PACKET_SIZE); 253 | loop { 254 | match tokio::time::timeout( 255 | Duration::from_millis(udp_timeout_ms), 256 | TunnelMessage::recv_raw(&mut quic_recv, &mut buf), 257 | ) 258 | .await 259 | { 260 | Ok(Ok(len)) => { 261 | udp_socket 262 | .send(&buf[..len as usize]) 263 | .await 264 | .context("failed to send datagram through udp_socket")?; 265 | } 266 | e => { 267 | match e { 268 | Ok(Err(e)) => { 269 | warn!("failed to read from udp packet from tunnel, err: {e:?}"); 270 | } 271 | Err(_) => { 272 | debug!("timeout on reading udp packet from tunnel"); 273 | } 274 | _ => unreachable!(""), 275 | } 276 | break; 277 | } 278 | } 279 | } 280 | 281 | Ok::<(), anyhow::Error>(()) 282 | } 283 | } 284 | --------------------------------------------------------------------------------