├── .github ├── FUNDING.yml └── workflows │ ├── build-binary.yml │ └── build-docker-image.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── config.toml ├── rust-toolchain.toml └── src ├── auth ├── from_config.rs ├── mod.rs └── redis.rs ├── common.rs ├── inbound ├── mod.rs ├── tls.rs ├── trojan.rs └── trojan │ └── fallback.rs ├── main.rs ├── outbound ├── direct.rs └── mod.rs ├── relay.rs ├── server.rs └── utils ├── acl.rs ├── config.rs ├── count_stream.rs ├── logger.rs ├── mod.rs ├── peekable_stream.rs └── wildcard_match.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: llc1123 4 | -------------------------------------------------------------------------------- /.github/workflows/build-binary.yml: -------------------------------------------------------------------------------- 1 | name: build-binary 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-windows: 7 | strategy: 8 | matrix: 9 | target: [x86_64-pc-windows-msvc] 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: nightly 16 | target: ${{ matrix.target }} 17 | override: true 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | toolchain: nightly 21 | command: build 22 | args: --release --target ${{ matrix.target }} 23 | - uses: actions/upload-artifact@v2 24 | with: 25 | name: build-windows 26 | path: ./target/${{ matrix.target }}/release/trojan-rust.exe 27 | - uses: svenstaro/upload-release-action@v1-release 28 | if: startsWith(github.ref, 'refs/tags/') 29 | with: 30 | repo_token: ${{ secrets.GITHUB_TOKEN }} 31 | file: ./target/${{ matrix.target }}/release/trojan-rust.exe 32 | asset_name: trojan-rust-${{ matrix.target }}.exe 33 | tag: ${{ github.ref }} 34 | 35 | build-linux: 36 | strategy: 37 | matrix: 38 | target: [x86_64-unknown-linux-musl, aarch64-unknown-linux-musl] 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | toolchain: nightly 45 | target: ${{ matrix.target }} 46 | override: true 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | use-cross: true 50 | toolchain: nightly 51 | command: build 52 | args: --release --target ${{ matrix.target }} 53 | - uses: actions/upload-artifact@v2 54 | with: 55 | name: build-${{ matrix.target }} 56 | path: ./target/${{ matrix.target }}/release/trojan-rust 57 | - uses: svenstaro/upload-release-action@v1-release 58 | if: startsWith(github.ref, 'refs/tags/') 59 | with: 60 | repo_token: ${{ secrets.GITHUB_TOKEN }} 61 | file: ./target/${{ matrix.target }}/release/trojan-rust 62 | asset_name: trojan-rust-${{ matrix.target }} 63 | tag: ${{ github.ref }} 64 | -------------------------------------------------------------------------------- /.github/workflows/build-docker-image.yml: -------------------------------------------------------------------------------- 1 | name: build-docker-image 2 | 3 | on: [push] 4 | 5 | jobs: 6 | docker: 7 | runs-on: ubuntu-latest 8 | env: 9 | CARGO_NET_GIT_FETCH_WITH_CLI: true 10 | steps: 11 | - 12 | name: Checkout 13 | uses: actions/checkout@v3 14 | - 15 | name: Docker meta 16 | id: meta 17 | uses: docker/metadata-action@v4 18 | with: 19 | images: | 20 | llc1123/trojan-rust 21 | ghcr.io/llc1123/trojan-rust 22 | tags: | 23 | type=edge 24 | type=semver,pattern={{version}} 25 | - 26 | name: Set up QEMU 27 | uses: docker/setup-qemu-action@v2 28 | - 29 | name: Set up Docker Buildx 30 | uses: docker/setup-buildx-action@v2 31 | - 32 | name: Login to DockerHub 33 | uses: docker/login-action@v2 34 | with: 35 | username: ${{ secrets.DOCKERHUB_USERNAME }} 36 | password: ${{ secrets.DOCKERHUB_TOKEN }} 37 | - 38 | name: Login to GitHub Container Registry 39 | uses: docker/login-action@v2 40 | with: 41 | registry: ghcr.io 42 | username: ${{ github.repository_owner }} 43 | password: ${{ secrets.GITHUB_TOKEN }} 44 | - 45 | name: Build and push 46 | uses: docker/build-push-action@v3 47 | with: 48 | context: . 49 | platforms: linux/amd64,linux/arm64 50 | push: true 51 | tags: ${{ steps.meta.outputs.tags }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | .idea 4 | *.pem -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.0 2 | - Implement TCP tunnel properly 3 | - Handles redis connections properly 4 | - Reduce memory usage 5 | 6 | ## 1.3.0 7 | - Fully support sni wildcard matching 8 | - Bugfix 9 | 10 | ## 1.2.3 11 | - Bugfix 12 | - Drop acl blocked udp packet 13 | 14 | ## 1.2.2 15 | - Workaround for Sink::with bug which will cause panic on udp. 16 | 17 | ## 1.2.1 18 | - Fix ACL blocking requests incorrectly 19 | 20 | ## 1.2.0 21 | - Support blocking requests targeting local and private network. 22 | 23 | ## 1.1.0 24 | - Support multiple hostnames and default SAN in certificate 25 | 26 | ## 1.0.2 27 | - Add TCP timeout to eliminate half open connections 28 | 29 | ## 1.0.1 30 | - Refine CI scripts 31 | 32 | ## 1.0.0 33 | - Initial Release -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "android_system_properties" 7 | version = "0.1.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 10 | dependencies = [ 11 | "libc", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.68" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" 19 | 20 | [[package]] 21 | name = "arc-swap" 22 | version = "1.6.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" 25 | 26 | [[package]] 27 | name = "async-trait" 28 | version = "0.1.63" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" 31 | dependencies = [ 32 | "proc-macro2", 33 | "quote", 34 | "syn", 35 | ] 36 | 37 | [[package]] 38 | name = "atty" 39 | version = "0.2.14" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 42 | dependencies = [ 43 | "hermit-abi 0.1.19", 44 | "libc", 45 | "winapi", 46 | ] 47 | 48 | [[package]] 49 | name = "autocfg" 50 | version = "1.1.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 53 | 54 | [[package]] 55 | name = "base64" 56 | version = "0.13.1" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 59 | 60 | [[package]] 61 | name = "bitflags" 62 | version = "1.3.2" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 65 | 66 | [[package]] 67 | name = "block-buffer" 68 | version = "0.10.3" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 71 | dependencies = [ 72 | "generic-array", 73 | ] 74 | 75 | [[package]] 76 | name = "bumpalo" 77 | version = "3.12.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 80 | 81 | [[package]] 82 | name = "bytes" 83 | version = "1.3.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 86 | 87 | [[package]] 88 | name = "cc" 89 | version = "1.0.78" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 92 | 93 | [[package]] 94 | name = "cfg-if" 95 | version = "1.0.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 98 | 99 | [[package]] 100 | name = "chrono" 101 | version = "0.4.23" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" 104 | dependencies = [ 105 | "iana-time-zone", 106 | "js-sys", 107 | "num-integer", 108 | "num-traits", 109 | "serde", 110 | "time 0.1.45", 111 | "wasm-bindgen", 112 | "winapi", 113 | ] 114 | 115 | [[package]] 116 | name = "clap" 117 | version = "4.1.4" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" 120 | dependencies = [ 121 | "bitflags", 122 | "clap_derive", 123 | "clap_lex", 124 | "is-terminal", 125 | "once_cell", 126 | "strsim", 127 | "termcolor", 128 | ] 129 | 130 | [[package]] 131 | name = "clap_derive" 132 | version = "4.1.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 135 | dependencies = [ 136 | "heck", 137 | "proc-macro-error", 138 | "proc-macro2", 139 | "quote", 140 | "syn", 141 | ] 142 | 143 | [[package]] 144 | name = "clap_lex" 145 | version = "0.3.1" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" 148 | dependencies = [ 149 | "os_str_bytes", 150 | ] 151 | 152 | [[package]] 153 | name = "codespan-reporting" 154 | version = "0.11.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 157 | dependencies = [ 158 | "termcolor", 159 | "unicode-width", 160 | ] 161 | 162 | [[package]] 163 | name = "colored" 164 | version = "1.9.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" 167 | dependencies = [ 168 | "atty", 169 | "lazy_static", 170 | "winapi", 171 | ] 172 | 173 | [[package]] 174 | name = "combine" 175 | version = "4.6.6" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" 178 | dependencies = [ 179 | "bytes", 180 | "futures-core", 181 | "memchr", 182 | "pin-project-lite", 183 | "tokio", 184 | "tokio-util", 185 | ] 186 | 187 | [[package]] 188 | name = "core-foundation-sys" 189 | version = "0.8.3" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 192 | 193 | [[package]] 194 | name = "cpufeatures" 195 | version = "0.2.5" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 198 | dependencies = [ 199 | "libc", 200 | ] 201 | 202 | [[package]] 203 | name = "crypto-common" 204 | version = "0.1.6" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 207 | dependencies = [ 208 | "generic-array", 209 | "typenum", 210 | ] 211 | 212 | [[package]] 213 | name = "cxx" 214 | version = "1.0.87" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "b61a7545f753a88bcbe0a70de1fcc0221e10bfc752f576754fa91e663db1622e" 217 | dependencies = [ 218 | "cc", 219 | "cxxbridge-flags", 220 | "cxxbridge-macro", 221 | "link-cplusplus", 222 | ] 223 | 224 | [[package]] 225 | name = "cxx-build" 226 | version = "1.0.87" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "f464457d494b5ed6905c63b0c4704842aba319084a0a3561cdc1359536b53200" 229 | dependencies = [ 230 | "cc", 231 | "codespan-reporting", 232 | "once_cell", 233 | "proc-macro2", 234 | "quote", 235 | "scratch", 236 | "syn", 237 | ] 238 | 239 | [[package]] 240 | name = "cxxbridge-flags" 241 | version = "1.0.87" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "43c7119ce3a3701ed81aca8410b9acf6fc399d2629d057b87e2efa4e63a3aaea" 244 | 245 | [[package]] 246 | name = "cxxbridge-macro" 247 | version = "1.0.87" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e" 250 | dependencies = [ 251 | "proc-macro2", 252 | "quote", 253 | "syn", 254 | ] 255 | 256 | [[package]] 257 | name = "darling" 258 | version = "0.14.2" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" 261 | dependencies = [ 262 | "darling_core", 263 | "darling_macro", 264 | ] 265 | 266 | [[package]] 267 | name = "darling_core" 268 | version = "0.14.2" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" 271 | dependencies = [ 272 | "fnv", 273 | "ident_case", 274 | "proc-macro2", 275 | "quote", 276 | "strsim", 277 | "syn", 278 | ] 279 | 280 | [[package]] 281 | name = "darling_macro" 282 | version = "0.14.2" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" 285 | dependencies = [ 286 | "darling_core", 287 | "quote", 288 | "syn", 289 | ] 290 | 291 | [[package]] 292 | name = "digest" 293 | version = "0.10.6" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 296 | dependencies = [ 297 | "block-buffer", 298 | "crypto-common", 299 | ] 300 | 301 | [[package]] 302 | name = "errno" 303 | version = "0.2.8" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 306 | dependencies = [ 307 | "errno-dragonfly", 308 | "libc", 309 | "winapi", 310 | ] 311 | 312 | [[package]] 313 | name = "errno-dragonfly" 314 | version = "0.1.2" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 317 | dependencies = [ 318 | "cc", 319 | "libc", 320 | ] 321 | 322 | [[package]] 323 | name = "fern" 324 | version = "0.6.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a" 327 | dependencies = [ 328 | "colored", 329 | "log", 330 | ] 331 | 332 | [[package]] 333 | name = "fnv" 334 | version = "1.0.7" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 337 | 338 | [[package]] 339 | name = "foreign-types" 340 | version = "0.3.2" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 343 | dependencies = [ 344 | "foreign-types-shared", 345 | ] 346 | 347 | [[package]] 348 | name = "foreign-types-shared" 349 | version = "0.1.1" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 352 | 353 | [[package]] 354 | name = "form_urlencoded" 355 | version = "1.1.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 358 | dependencies = [ 359 | "percent-encoding", 360 | ] 361 | 362 | [[package]] 363 | name = "futures" 364 | version = "0.3.25" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 367 | dependencies = [ 368 | "futures-channel", 369 | "futures-core", 370 | "futures-executor", 371 | "futures-io", 372 | "futures-sink", 373 | "futures-task", 374 | "futures-util", 375 | ] 376 | 377 | [[package]] 378 | name = "futures-channel" 379 | version = "0.3.25" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 382 | dependencies = [ 383 | "futures-core", 384 | "futures-sink", 385 | ] 386 | 387 | [[package]] 388 | name = "futures-core" 389 | version = "0.3.25" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 392 | 393 | [[package]] 394 | name = "futures-executor" 395 | version = "0.3.25" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 398 | dependencies = [ 399 | "futures-core", 400 | "futures-task", 401 | "futures-util", 402 | ] 403 | 404 | [[package]] 405 | name = "futures-io" 406 | version = "0.3.25" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 409 | 410 | [[package]] 411 | name = "futures-macro" 412 | version = "0.3.25" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 415 | dependencies = [ 416 | "proc-macro2", 417 | "quote", 418 | "syn", 419 | ] 420 | 421 | [[package]] 422 | name = "futures-sink" 423 | version = "0.3.25" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 426 | 427 | [[package]] 428 | name = "futures-task" 429 | version = "0.3.25" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 432 | 433 | [[package]] 434 | name = "futures-util" 435 | version = "0.3.25" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 438 | dependencies = [ 439 | "futures-channel", 440 | "futures-core", 441 | "futures-io", 442 | "futures-macro", 443 | "futures-sink", 444 | "futures-task", 445 | "memchr", 446 | "pin-project-lite", 447 | "pin-utils", 448 | "slab", 449 | ] 450 | 451 | [[package]] 452 | name = "generic-array" 453 | version = "0.14.6" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 456 | dependencies = [ 457 | "typenum", 458 | "version_check", 459 | ] 460 | 461 | [[package]] 462 | name = "hashbrown" 463 | version = "0.12.3" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 466 | 467 | [[package]] 468 | name = "heck" 469 | version = "0.4.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 472 | 473 | [[package]] 474 | name = "hermit-abi" 475 | version = "0.1.19" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 478 | dependencies = [ 479 | "libc", 480 | ] 481 | 482 | [[package]] 483 | name = "hermit-abi" 484 | version = "0.2.6" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 487 | dependencies = [ 488 | "libc", 489 | ] 490 | 491 | [[package]] 492 | name = "hex" 493 | version = "0.4.3" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 496 | 497 | [[package]] 498 | name = "http" 499 | version = "0.2.8" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 502 | dependencies = [ 503 | "bytes", 504 | "fnv", 505 | "itoa", 506 | ] 507 | 508 | [[package]] 509 | name = "http-body" 510 | version = "0.4.5" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 513 | dependencies = [ 514 | "bytes", 515 | "http", 516 | "pin-project-lite", 517 | ] 518 | 519 | [[package]] 520 | name = "httparse" 521 | version = "1.8.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 524 | 525 | [[package]] 526 | name = "httpdate" 527 | version = "1.0.2" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 530 | 531 | [[package]] 532 | name = "hyper" 533 | version = "0.14.23" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" 536 | dependencies = [ 537 | "bytes", 538 | "futures-channel", 539 | "futures-core", 540 | "futures-util", 541 | "http", 542 | "http-body", 543 | "httparse", 544 | "httpdate", 545 | "itoa", 546 | "pin-project-lite", 547 | "tokio", 548 | "tower-service", 549 | "tracing", 550 | "want", 551 | ] 552 | 553 | [[package]] 554 | name = "iana-time-zone" 555 | version = "0.1.53" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 558 | dependencies = [ 559 | "android_system_properties", 560 | "core-foundation-sys", 561 | "iana-time-zone-haiku", 562 | "js-sys", 563 | "wasm-bindgen", 564 | "winapi", 565 | ] 566 | 567 | [[package]] 568 | name = "iana-time-zone-haiku" 569 | version = "0.1.1" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 572 | dependencies = [ 573 | "cxx", 574 | "cxx-build", 575 | ] 576 | 577 | [[package]] 578 | name = "ident_case" 579 | version = "1.0.1" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 582 | 583 | [[package]] 584 | name = "idna" 585 | version = "0.3.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 588 | dependencies = [ 589 | "unicode-bidi", 590 | "unicode-normalization", 591 | ] 592 | 593 | [[package]] 594 | name = "indexmap" 595 | version = "1.9.2" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 598 | dependencies = [ 599 | "autocfg", 600 | "hashbrown", 601 | "serde", 602 | ] 603 | 604 | [[package]] 605 | name = "io-lifetimes" 606 | version = "1.0.4" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" 609 | dependencies = [ 610 | "libc", 611 | "windows-sys", 612 | ] 613 | 614 | [[package]] 615 | name = "is-terminal" 616 | version = "0.4.2" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" 619 | dependencies = [ 620 | "hermit-abi 0.2.6", 621 | "io-lifetimes", 622 | "rustix", 623 | "windows-sys", 624 | ] 625 | 626 | [[package]] 627 | name = "itoa" 628 | version = "1.0.5" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 631 | 632 | [[package]] 633 | name = "js-sys" 634 | version = "0.3.60" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 637 | dependencies = [ 638 | "wasm-bindgen", 639 | ] 640 | 641 | [[package]] 642 | name = "lazy_static" 643 | version = "1.4.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 646 | 647 | [[package]] 648 | name = "libc" 649 | version = "0.2.139" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 652 | 653 | [[package]] 654 | name = "link-cplusplus" 655 | version = "1.0.8" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" 658 | dependencies = [ 659 | "cc", 660 | ] 661 | 662 | [[package]] 663 | name = "linux-raw-sys" 664 | version = "0.1.4" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 667 | 668 | [[package]] 669 | name = "lock_api" 670 | version = "0.4.9" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 673 | dependencies = [ 674 | "autocfg", 675 | "scopeguard", 676 | ] 677 | 678 | [[package]] 679 | name = "log" 680 | version = "0.4.17" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 683 | dependencies = [ 684 | "cfg-if", 685 | ] 686 | 687 | [[package]] 688 | name = "memchr" 689 | version = "2.5.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 692 | 693 | [[package]] 694 | name = "mio" 695 | version = "0.8.5" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 698 | dependencies = [ 699 | "libc", 700 | "log", 701 | "wasi 0.11.0+wasi-snapshot-preview1", 702 | "windows-sys", 703 | ] 704 | 705 | [[package]] 706 | name = "num-integer" 707 | version = "0.1.45" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 710 | dependencies = [ 711 | "autocfg", 712 | "num-traits", 713 | ] 714 | 715 | [[package]] 716 | name = "num-traits" 717 | version = "0.2.15" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 720 | dependencies = [ 721 | "autocfg", 722 | ] 723 | 724 | [[package]] 725 | name = "num_cpus" 726 | version = "1.15.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 729 | dependencies = [ 730 | "hermit-abi 0.2.6", 731 | "libc", 732 | ] 733 | 734 | [[package]] 735 | name = "once_cell" 736 | version = "1.17.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 739 | 740 | [[package]] 741 | name = "openssl" 742 | version = "0.10.45" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" 745 | dependencies = [ 746 | "bitflags", 747 | "cfg-if", 748 | "foreign-types", 749 | "libc", 750 | "once_cell", 751 | "openssl-macros", 752 | "openssl-sys", 753 | ] 754 | 755 | [[package]] 756 | name = "openssl-macros" 757 | version = "0.1.0" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 760 | dependencies = [ 761 | "proc-macro2", 762 | "quote", 763 | "syn", 764 | ] 765 | 766 | [[package]] 767 | name = "openssl-src" 768 | version = "111.24.0+1.1.1s" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" 771 | dependencies = [ 772 | "cc", 773 | ] 774 | 775 | [[package]] 776 | name = "openssl-sys" 777 | version = "0.9.80" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" 780 | dependencies = [ 781 | "autocfg", 782 | "cc", 783 | "libc", 784 | "openssl-src", 785 | "pkg-config", 786 | "vcpkg", 787 | ] 788 | 789 | [[package]] 790 | name = "os_str_bytes" 791 | version = "6.4.1" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 794 | 795 | [[package]] 796 | name = "parking_lot" 797 | version = "0.12.1" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 800 | dependencies = [ 801 | "lock_api", 802 | "parking_lot_core", 803 | ] 804 | 805 | [[package]] 806 | name = "parking_lot_core" 807 | version = "0.9.6" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" 810 | dependencies = [ 811 | "cfg-if", 812 | "libc", 813 | "redox_syscall", 814 | "smallvec", 815 | "windows-sys", 816 | ] 817 | 818 | [[package]] 819 | name = "percent-encoding" 820 | version = "2.2.0" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 823 | 824 | [[package]] 825 | name = "pin-project-lite" 826 | version = "0.2.9" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 829 | 830 | [[package]] 831 | name = "pin-utils" 832 | version = "0.1.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 835 | 836 | [[package]] 837 | name = "pkg-config" 838 | version = "0.3.26" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 841 | 842 | [[package]] 843 | name = "proc-macro-error" 844 | version = "1.0.4" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 847 | dependencies = [ 848 | "proc-macro-error-attr", 849 | "proc-macro2", 850 | "quote", 851 | "syn", 852 | "version_check", 853 | ] 854 | 855 | [[package]] 856 | name = "proc-macro-error-attr" 857 | version = "1.0.4" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 860 | dependencies = [ 861 | "proc-macro2", 862 | "quote", 863 | "version_check", 864 | ] 865 | 866 | [[package]] 867 | name = "proc-macro2" 868 | version = "1.0.50" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" 871 | dependencies = [ 872 | "unicode-ident", 873 | ] 874 | 875 | [[package]] 876 | name = "quote" 877 | version = "1.0.23" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 880 | dependencies = [ 881 | "proc-macro2", 882 | ] 883 | 884 | [[package]] 885 | name = "redis" 886 | version = "0.22.3" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "aa8455fa3621f6b41c514946de66ea0531f57ca017b2e6c7cc368035ea5b46df" 889 | dependencies = [ 890 | "arc-swap", 891 | "async-trait", 892 | "bytes", 893 | "combine", 894 | "futures", 895 | "futures-util", 896 | "itoa", 897 | "percent-encoding", 898 | "pin-project-lite", 899 | "ryu", 900 | "sha1_smol", 901 | "tokio", 902 | "tokio-util", 903 | "url", 904 | ] 905 | 906 | [[package]] 907 | name = "redox_syscall" 908 | version = "0.2.16" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 911 | dependencies = [ 912 | "bitflags", 913 | ] 914 | 915 | [[package]] 916 | name = "rustix" 917 | version = "0.36.7" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" 920 | dependencies = [ 921 | "bitflags", 922 | "errno", 923 | "io-lifetimes", 924 | "libc", 925 | "linux-raw-sys", 926 | "windows-sys", 927 | ] 928 | 929 | [[package]] 930 | name = "ryu" 931 | version = "1.0.12" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 934 | 935 | [[package]] 936 | name = "scopeguard" 937 | version = "1.1.0" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 940 | 941 | [[package]] 942 | name = "scratch" 943 | version = "1.0.3" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" 946 | 947 | [[package]] 948 | name = "serde" 949 | version = "1.0.152" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 952 | dependencies = [ 953 | "serde_derive", 954 | ] 955 | 956 | [[package]] 957 | name = "serde_derive" 958 | version = "1.0.152" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 961 | dependencies = [ 962 | "proc-macro2", 963 | "quote", 964 | "syn", 965 | ] 966 | 967 | [[package]] 968 | name = "serde_json" 969 | version = "1.0.91" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" 972 | dependencies = [ 973 | "itoa", 974 | "ryu", 975 | "serde", 976 | ] 977 | 978 | [[package]] 979 | name = "serde_with" 980 | version = "2.2.0" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "30d904179146de381af4c93d3af6ca4984b3152db687dacb9c3c35e86f39809c" 983 | dependencies = [ 984 | "base64", 985 | "chrono", 986 | "hex", 987 | "indexmap", 988 | "serde", 989 | "serde_json", 990 | "serde_with_macros", 991 | "time 0.3.17", 992 | ] 993 | 994 | [[package]] 995 | name = "serde_with_macros" 996 | version = "2.2.0" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "a1966009f3c05f095697c537312f5415d1e3ed31ce0a56942bac4c771c5c335e" 999 | dependencies = [ 1000 | "darling", 1001 | "proc-macro2", 1002 | "quote", 1003 | "syn", 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "sha1_smol" 1008 | version = "1.0.0" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" 1011 | 1012 | [[package]] 1013 | name = "sha2" 1014 | version = "0.10.6" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 1017 | dependencies = [ 1018 | "cfg-if", 1019 | "cpufeatures", 1020 | "digest", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "signal-hook-registry" 1025 | version = "1.4.0" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1028 | dependencies = [ 1029 | "libc", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "slab" 1034 | version = "0.4.7" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 1037 | dependencies = [ 1038 | "autocfg", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "smallvec" 1043 | version = "1.10.0" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1046 | 1047 | [[package]] 1048 | name = "socket2" 1049 | version = "0.4.7" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 1052 | dependencies = [ 1053 | "libc", 1054 | "winapi", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "socks5-protocol" 1059 | version = "0.3.5" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "24b4925e3858bd0a1ac69668653234b4bebb0d4cd32a111c31057837d3d99117" 1062 | dependencies = [ 1063 | "thiserror", 1064 | "tokio", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "strsim" 1069 | version = "0.10.0" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1072 | 1073 | [[package]] 1074 | name = "syn" 1075 | version = "1.0.107" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 1078 | dependencies = [ 1079 | "proc-macro2", 1080 | "quote", 1081 | "unicode-ident", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "termcolor" 1086 | version = "1.2.0" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 1089 | dependencies = [ 1090 | "winapi-util", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "thiserror" 1095 | version = "1.0.38" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 1098 | dependencies = [ 1099 | "thiserror-impl", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "thiserror-impl" 1104 | version = "1.0.38" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 1107 | dependencies = [ 1108 | "proc-macro2", 1109 | "quote", 1110 | "syn", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "time" 1115 | version = "0.1.45" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 1118 | dependencies = [ 1119 | "libc", 1120 | "wasi 0.10.0+wasi-snapshot-preview1", 1121 | "winapi", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "time" 1126 | version = "0.3.17" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" 1129 | dependencies = [ 1130 | "itoa", 1131 | "serde", 1132 | "time-core", 1133 | "time-macros", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "time-core" 1138 | version = "0.1.0" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" 1141 | 1142 | [[package]] 1143 | name = "time-macros" 1144 | version = "0.2.6" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" 1147 | dependencies = [ 1148 | "time-core", 1149 | ] 1150 | 1151 | [[package]] 1152 | name = "tinyvec" 1153 | version = "1.6.0" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1156 | dependencies = [ 1157 | "tinyvec_macros", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "tinyvec_macros" 1162 | version = "0.1.0" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1165 | 1166 | [[package]] 1167 | name = "tokio" 1168 | version = "1.24.2" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" 1171 | dependencies = [ 1172 | "autocfg", 1173 | "bytes", 1174 | "libc", 1175 | "memchr", 1176 | "mio", 1177 | "num_cpus", 1178 | "parking_lot", 1179 | "pin-project-lite", 1180 | "signal-hook-registry", 1181 | "socket2", 1182 | "tokio-macros", 1183 | "windows-sys", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "tokio-io-timeout" 1188 | version = "1.2.0" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" 1191 | dependencies = [ 1192 | "pin-project-lite", 1193 | "tokio", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "tokio-macros" 1198 | version = "1.8.2" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 1201 | dependencies = [ 1202 | "proc-macro2", 1203 | "quote", 1204 | "syn", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "tokio-openssl" 1209 | version = "0.6.3" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" 1212 | dependencies = [ 1213 | "futures-util", 1214 | "openssl", 1215 | "openssl-sys", 1216 | "tokio", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "tokio-stream" 1221 | version = "0.1.11" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" 1224 | dependencies = [ 1225 | "futures-core", 1226 | "pin-project-lite", 1227 | "tokio", 1228 | "tokio-util", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "tokio-util" 1233 | version = "0.7.4" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 1236 | dependencies = [ 1237 | "bytes", 1238 | "futures-core", 1239 | "futures-sink", 1240 | "pin-project-lite", 1241 | "tokio", 1242 | "tracing", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "toml" 1247 | version = "0.5.11" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 1250 | dependencies = [ 1251 | "serde", 1252 | ] 1253 | 1254 | [[package]] 1255 | name = "tower-service" 1256 | version = "0.3.2" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1259 | 1260 | [[package]] 1261 | name = "tracing" 1262 | version = "0.1.37" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1265 | dependencies = [ 1266 | "cfg-if", 1267 | "pin-project-lite", 1268 | "tracing-core", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "tracing-core" 1273 | version = "0.1.30" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1276 | dependencies = [ 1277 | "once_cell", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "trojan-rust" 1282 | version = "1.4.0" 1283 | dependencies = [ 1284 | "anyhow", 1285 | "async-trait", 1286 | "bytes", 1287 | "chrono", 1288 | "clap", 1289 | "fern", 1290 | "futures", 1291 | "hex", 1292 | "http", 1293 | "hyper", 1294 | "log", 1295 | "openssl", 1296 | "redis", 1297 | "serde", 1298 | "serde_derive", 1299 | "serde_with", 1300 | "sha2", 1301 | "socks5-protocol", 1302 | "tokio", 1303 | "tokio-io-timeout", 1304 | "tokio-openssl", 1305 | "tokio-stream", 1306 | "tokio-util", 1307 | "toml", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "try-lock" 1312 | version = "0.2.4" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 1315 | 1316 | [[package]] 1317 | name = "typenum" 1318 | version = "1.16.0" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1321 | 1322 | [[package]] 1323 | name = "unicode-bidi" 1324 | version = "0.3.10" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" 1327 | 1328 | [[package]] 1329 | name = "unicode-ident" 1330 | version = "1.0.6" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 1333 | 1334 | [[package]] 1335 | name = "unicode-normalization" 1336 | version = "0.1.22" 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" 1338 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1339 | dependencies = [ 1340 | "tinyvec", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "unicode-width" 1345 | version = "0.1.10" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1348 | 1349 | [[package]] 1350 | name = "url" 1351 | version = "2.3.1" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 1354 | dependencies = [ 1355 | "form_urlencoded", 1356 | "idna", 1357 | "percent-encoding", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "vcpkg" 1362 | version = "0.2.15" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1365 | 1366 | [[package]] 1367 | name = "version_check" 1368 | version = "0.9.4" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1371 | 1372 | [[package]] 1373 | name = "want" 1374 | version = "0.3.0" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1377 | dependencies = [ 1378 | "log", 1379 | "try-lock", 1380 | ] 1381 | 1382 | [[package]] 1383 | name = "wasi" 1384 | version = "0.10.0+wasi-snapshot-preview1" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1387 | 1388 | [[package]] 1389 | name = "wasi" 1390 | version = "0.11.0+wasi-snapshot-preview1" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1393 | 1394 | [[package]] 1395 | name = "wasm-bindgen" 1396 | version = "0.2.83" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 1399 | dependencies = [ 1400 | "cfg-if", 1401 | "wasm-bindgen-macro", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "wasm-bindgen-backend" 1406 | version = "0.2.83" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 1409 | dependencies = [ 1410 | "bumpalo", 1411 | "log", 1412 | "once_cell", 1413 | "proc-macro2", 1414 | "quote", 1415 | "syn", 1416 | "wasm-bindgen-shared", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "wasm-bindgen-macro" 1421 | version = "0.2.83" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 1424 | dependencies = [ 1425 | "quote", 1426 | "wasm-bindgen-macro-support", 1427 | ] 1428 | 1429 | [[package]] 1430 | name = "wasm-bindgen-macro-support" 1431 | version = "0.2.83" 1432 | source = "registry+https://github.com/rust-lang/crates.io-index" 1433 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 1434 | dependencies = [ 1435 | "proc-macro2", 1436 | "quote", 1437 | "syn", 1438 | "wasm-bindgen-backend", 1439 | "wasm-bindgen-shared", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "wasm-bindgen-shared" 1444 | version = "0.2.83" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 1447 | 1448 | [[package]] 1449 | name = "winapi" 1450 | version = "0.3.9" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1453 | dependencies = [ 1454 | "winapi-i686-pc-windows-gnu", 1455 | "winapi-x86_64-pc-windows-gnu", 1456 | ] 1457 | 1458 | [[package]] 1459 | name = "winapi-i686-pc-windows-gnu" 1460 | version = "0.4.0" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1463 | 1464 | [[package]] 1465 | name = "winapi-util" 1466 | version = "0.1.5" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1469 | dependencies = [ 1470 | "winapi", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "winapi-x86_64-pc-windows-gnu" 1475 | version = "0.4.0" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1478 | 1479 | [[package]] 1480 | name = "windows-sys" 1481 | version = "0.42.0" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1484 | dependencies = [ 1485 | "windows_aarch64_gnullvm", 1486 | "windows_aarch64_msvc", 1487 | "windows_i686_gnu", 1488 | "windows_i686_msvc", 1489 | "windows_x86_64_gnu", 1490 | "windows_x86_64_gnullvm", 1491 | "windows_x86_64_msvc", 1492 | ] 1493 | 1494 | [[package]] 1495 | name = "windows_aarch64_gnullvm" 1496 | version = "0.42.1" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 1499 | 1500 | [[package]] 1501 | name = "windows_aarch64_msvc" 1502 | version = "0.42.1" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 1505 | 1506 | [[package]] 1507 | name = "windows_i686_gnu" 1508 | version = "0.42.1" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 1511 | 1512 | [[package]] 1513 | name = "windows_i686_msvc" 1514 | version = "0.42.1" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 1517 | 1518 | [[package]] 1519 | name = "windows_x86_64_gnu" 1520 | version = "0.42.1" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 1523 | 1524 | [[package]] 1525 | name = "windows_x86_64_gnullvm" 1526 | version = "0.42.1" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 1529 | 1530 | [[package]] 1531 | name = "windows_x86_64_msvc" 1532 | version = "0.42.1" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 1535 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trojan-rust" 3 | version = "1.4.0" 4 | edition = "2018" 5 | authors = ["llc1123 ", "imspace "] 6 | description = "Yet another trojan-gfw implementation in Rust" 7 | publish = false 8 | 9 | [profile.release] 10 | lto = true 11 | codegen-units = 1 12 | panic = 'abort' 13 | strip = "symbols" 14 | 15 | [dependencies] 16 | anyhow = "1.0.66" 17 | chrono = "0.4.22" 18 | clap = { version = "4.0.18", features = ["derive", "env"] } 19 | fern = { version = "0.6.1", features = ["colored"] } 20 | log = "0.4.17" 21 | serde = "1.0.147" 22 | serde_derive = "1.0.147" 23 | tokio = { version = "1.21.2", features = ["full"] } 24 | tokio-stream = { version = "0.1.11", features = ["sync"] } 25 | toml = "0.5.9" 26 | hyper = { version = "0.14.20", features = ["http1", "server"] } 27 | http = "0.2.8" 28 | futures = "0.3.25" 29 | socks5-protocol = "0.3.5" 30 | tokio-util = { version = "0.7.4", features = ["codec", "net"] } 31 | bytes = "1.2.1" 32 | async-trait = "0.1.58" 33 | sha2 = "0.10.6" 34 | hex = "0.4.3" 35 | redis = { version = "0.22.1", features = ["tokio-comp", "connection-manager"] } 36 | tokio-openssl = "0.6.3" 37 | openssl = { version = "0.10.42", features = ["vendored"] } 38 | tokio-io-timeout = "1.2.0" 39 | serde_with = "2.0.1" 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rustlang/rust:nightly-bullseye-slim AS builder 2 | RUN apt-get update && apt-get install -y perl make && rm -rf /var/lib/apt/lists/* 3 | COPY Cargo.toml Cargo.lock ./ 4 | COPY src ./src 5 | RUN cargo install --path . 6 | 7 | FROM debian:bullseye-slim 8 | COPY --from=builder /usr/local/cargo/bin/trojan-rust /trojan-rust/ 9 | COPY config.toml /trojan-rust/example.toml 10 | WORKDIR /trojan-rust 11 | ENTRYPOINT [ "./trojan-rust", "-c" ] 12 | CMD [ "./example.toml" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 粒粒橙 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trojan-rust 2 | 3 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/llc1123/trojan-rust/blob/master/LICENSE) 4 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fllc1123%2Ftrojan-rust.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fllc1123%2Ftrojan-rust?ref=badge_shield) 5 | [![CI](https://img.shields.io/github/workflow/status/llc1123/trojan-rust/nightly)](https://github.com/llc1123/trojan-rust/actions) 6 | ![release version](https://img.shields.io/github/v/release/llc1123/trojan-rust) 7 | ![release downloads](https://img.shields.io/github/downloads/llc1123/trojan-rust/total) 8 | [![docker pulls](https://img.shields.io/docker/pulls/llc1123/trojan-rust)](https://hub.docker.com/r/llc1123/trojan-rust) 9 | [![docker image size](https://img.shields.io/docker/image-size/llc1123/trojan-rust/latest)](https://hub.docker.com/r/llc1123/trojan-rust) 10 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/llc1123/trojan-rust/pulls) 11 | 12 | Yet another [trojan-gfw](https://trojan-gfw.github.io/trojan/) implementation in Rust. 13 | 14 | ## Features 15 | - Server mode only (for now). 16 | - Supports Redis auth & flow stat. 17 | - Uses OpenSSL as crypto backend. 18 | - Uses tokio as async runtime. 19 | - Accurate flow stat (includes TLS overhead). 20 | 21 | ## How trojan handles connections 22 | 23 | - Not a TLS connection or TLS handshake failed: Connection Reset. (including SNI mismatch if not present in cert SAN) 24 | - SNI mismatch: Redirect to fallback 25 | - Expected TLS but not a trojan request: Redirect to fallback. 26 | - Trojan request but password incorrect: Redirect to fallback. 27 | - Trojan request and password correct: Work as a proxy tunnel. 28 | 29 | ## How the fallback server (usually) works 30 | 31 | - Not HTTP Request: 400 Bad Request 32 | - HTTP Request: 33 | - GET: 404 Not Found 34 | - Other: 405 Methon Not Allowed 35 | 36 | _This is like most cdn endpoints' behavior if you don't have a correct resource path._ 37 | 38 | ## Build 39 | ``` 40 | cargo build --release 41 | ``` 42 | 43 | ## Usage 44 | ``` 45 | USAGE: 46 | trojan-rust [OPTIONS] 47 | 48 | FLAGS: 49 | -h, --help Prints help information 50 | -V, --version Prints version information 51 | 52 | OPTIONS: 53 | -c, --config [default: config.toml] 54 | --log-level [env: LOGLEVEL=] [default: info] 55 | ``` 56 | 57 | ## Docker Image 58 | ```bash 59 | docker run -p 443:443 llc1123/trojan-rust example.toml 60 | # or use github container registry 61 | docker run -p 443:443 ghcr.io/llc1123/trojan-rust example.toml 62 | ``` 63 | 64 | ## Config 65 | 66 | example.toml 67 | 68 | ```toml 69 | # mode = "server" # optional 70 | 71 | ## uses default values if not present 72 | # [trojan] 73 | # password = [] # optional 74 | ## uses built-in if not present 75 | # fallback = "baidu.com:80" # optional 76 | 77 | [tls] 78 | # listen = "0.0.0.0:443" # optional 79 | # tcp_nodelay = false # optional 80 | # sni = [] # optional 81 | cert = "fullchain.pem" # required 82 | key = "privkey.pem" # required 83 | 84 | ## uses default values if not present 85 | # [outbound] 86 | ## Useful when you don't want your clients have access to your local network especially the redis server. 87 | # block_local = false # optional 88 | 89 | ## doesn't use redis if not present 90 | # [redis] 91 | # server = "127.0.0.1:6379" # optional 92 | ``` 93 | 94 | ## Multiple hostnames 95 | You can use as many hostnames as they are contained in the certificate provided. Or use all hostnames in the certificate SAN if left absent or empty. 96 | 97 | For example: 98 | ``` 99 | SAN in cert: a.example.com b.example.com 100 | SNI: [] 101 | a.example.com ✔️ 102 | b.example.com ✔️ 103 | other.com ❌ 104 | 105 | SAN in cert: a.example.com b.example.com 106 | SNI: ["a.example.com"] 107 | a.example.com ✔️ 108 | b.example.com ❌ 109 | other.com ❌ 110 | 111 | SAN in cert: a.example.com b.example.com 112 | SNI: ["a.example.com", "b.example.com"] 113 | a.example.com ✔️ 114 | b.example.com ✔️ 115 | other.com ❌ 116 | 117 | SAN in cert: a.example.com b.example.com 118 | SNI: ["c.example.com"] 119 | Error on startup 120 | ``` 121 | 122 | ## Wildcard SNI matching 123 | Trojan-rust supports wildcard certificates. 124 | 125 | For example: 126 | ``` 127 | SAN in cert: *.example.com 128 | SNI: [] 129 | a.example.com ✔️ 130 | b.example.com ✔️ 131 | example.com ❌ // doesn't match wildcard 132 | a.b.example.com ❌ // doesn't match wildcard 133 | other.com ❌ 134 | 135 | SAN in cert: *.example-a.com, *.example-b.com 136 | SNI: [] 137 | a.example-a.com ✔️ 138 | a.example-b.com ✔️ 139 | other.com ❌ 140 | 141 | SAN in cert: *.example.com, example.com 142 | SNI: [] 143 | example.com ✔️ 144 | a.example.com ✔️ 145 | other.com ❌ 146 | 147 | SAN in cert: *.example.com, example.com 148 | SNI: ["example.com"] 149 | example.com ✔️ 150 | a.example.com ❌ 151 | other.com ❌ 152 | 153 | SAN in cert: *.example.com 154 | SNI: ["a.example.com"] 155 | a.example.com ✔️ 156 | b.example.com ❌ 157 | other.com ❌ 158 | 159 | SAN in cert: *.example.com, example.com 160 | SNI: ["*.example.com"] 161 | a.example.com ✔️ 162 | b.example.com ✔️ 163 | example.com ❌ 164 | other.com ❌ 165 | ``` 166 | 167 | ## Redis Auth 168 | Add a user: 169 | ``` 170 | HSET [sha224(password)] download 0 upload 0 171 | ``` 172 | Trojan-rust checks if the hash exists in redis on each connection. If true, the user is authenticated and the flow will be recorded. 173 | 174 | Trojan-rust DOES NOT offer a method adding or removing a user. Please do it by yourself. 175 | 176 | **Don't forget to enable `block_local` feature to avoid attacks.** 177 | 178 | ## SSL key logging 179 | Enabled by setting environment variable `SSLKEYLOGFILE=filename`. 180 | 181 | **For debug use only. Never use in production.** 182 | 183 | 184 | ## TODO 185 | 186 | - [ ] Client mode 187 | - [ ] TPROXY mode 188 | - [ ] Benchmarks 189 | - [x] Wildcards in SNI config 190 | 191 | ## Contributing 192 | PRs welcome 193 | 194 | ## License 195 | Trojan-rust is [MIT licensed](https://github.com/llc1123/trojan-rust/blob/master/LICENSE). 196 | 197 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fllc1123%2Ftrojan-rust.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fllc1123%2Ftrojan-rust?ref=badge_large) -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # mode = "server" # optional 2 | 3 | ## uses default values if not present 4 | # [trojan] 5 | # password = [] # optional 6 | ## uses built-in if not present 7 | # fallback = "baidu.com:80" # optional 8 | 9 | [tls] 10 | # listen = "0.0.0.0:443" # optional 11 | # tcp_nodelay = false # optional 12 | # sni = [] # optional 13 | cert = "fullchain.pem" # required 14 | key = "privkey.pem" # required 15 | 16 | ## uses default values if not present 17 | # [outbound] 18 | # block_local = false # optional 19 | 20 | ## doesn't use redis if not present 21 | # [redis] 22 | # server = "127.0.0.1:6379" # optional -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = [ "rustfmt" ] 4 | profile = "minimal" 5 | -------------------------------------------------------------------------------- /src/auth/from_config.rs: -------------------------------------------------------------------------------- 1 | use super::Auth; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use sha2::{Digest, Sha224}; 5 | use std::collections::HashSet; 6 | 7 | pub struct ConfigAuthenticator { 8 | store: HashSet, 9 | } 10 | 11 | impl ConfigAuthenticator { 12 | pub fn new(passwords: Vec) -> Result { 13 | let mut s: HashSet = HashSet::new(); 14 | for p in passwords { 15 | let mut hasher = Sha224::new(); 16 | hasher.update(p.into_bytes()); 17 | let result = hasher.finalize(); 18 | s.insert(hex::encode(result)); 19 | } 20 | Ok(ConfigAuthenticator { store: s }) 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl Auth for ConfigAuthenticator { 26 | async fn auth(&self, password: &str) -> Result { 27 | Ok(self.store.contains(password)) 28 | } 29 | 30 | async fn stat(&self, password: &str, _: u64, _: u64) -> Result<()> { 31 | self.auth(password).await?; 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/auth/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod from_config; 2 | pub mod redis; 3 | 4 | use self::{from_config::ConfigAuthenticator, redis::RedisAuthenticator}; 5 | use crate::utils::config::Config; 6 | use anyhow::{anyhow, Context, Result}; 7 | use async_trait::async_trait; 8 | 9 | #[async_trait] 10 | pub trait Auth: Send + Sync + Unpin { 11 | async fn auth(&self, password: &str) -> Result; 12 | async fn stat(&self, password: &str, upload: u64, download: u64) -> Result<()>; 13 | } 14 | 15 | pub struct AuthHub { 16 | config_auth: ConfigAuthenticator, 17 | redis_auth: Option, 18 | } 19 | 20 | impl AuthHub { 21 | pub async fn new(config: &Config) -> Result { 22 | let config_auth = ConfigAuthenticator::new(config.trojan.password.clone())?; 23 | let mut redis_auth: Option = None; 24 | if let Some(redis) = &config.redis { 25 | redis_auth = Some( 26 | RedisAuthenticator::new(&redis.server) 27 | .await 28 | .context(format!("Unable to connect redis server {}", &redis.server))?, 29 | ); 30 | } 31 | Ok(AuthHub { 32 | config_auth, 33 | redis_auth, 34 | }) 35 | } 36 | } 37 | 38 | #[async_trait] 39 | impl Auth for AuthHub { 40 | async fn auth(&self, password: &str) -> Result { 41 | if self.config_auth.auth(&password).await? { 42 | return Ok(true); 43 | } 44 | if let Some(redis) = &self.redis_auth { 45 | return Ok(redis.auth(password).await?); 46 | } 47 | Ok(false) 48 | } 49 | 50 | async fn stat(&self, password: &str, upload: u64, download: u64) -> Result<()> { 51 | if self.config_auth.auth(password).await? { 52 | return Ok(self.config_auth.stat(password, upload, download).await?); 53 | } 54 | if let Some(redis) = &self.redis_auth { 55 | if redis.auth(password).await? { 56 | return Ok(redis.stat(password, upload, download).await?); 57 | } 58 | } 59 | Err(anyhow!("User {} not found.", &password)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/auth/redis.rs: -------------------------------------------------------------------------------- 1 | use super::Auth; 2 | use anyhow::{Context, Result}; 3 | use async_trait::async_trait; 4 | use log::info; 5 | use redis::aio::ConnectionManager; 6 | 7 | pub struct RedisAuthenticator { 8 | client: ConnectionManager, 9 | } 10 | 11 | impl RedisAuthenticator { 12 | pub async fn new(server: &str) -> Result { 13 | let client = redis::Client::open(format!("redis://{}/", server))?; 14 | let client = ConnectionManager::new(client) 15 | .await 16 | .context("Cannot create connection to redis server.")?; 17 | info!("Using redis auth: {}", server); 18 | Ok(RedisAuthenticator { client }) 19 | } 20 | } 21 | 22 | #[async_trait] 23 | impl Auth for RedisAuthenticator { 24 | async fn auth(&self, password: &str) -> Result { 25 | let mut client = self.client.clone(); 26 | Ok(redis::Cmd::exists(password) 27 | .query_async(&mut client) 28 | .await 29 | .context("Executing command EXISTS failed.")?) 30 | } 31 | 32 | async fn stat(&self, password: &str, upload: u64, download: u64) -> Result<()> { 33 | let mut client = self.client.clone(); 34 | Ok(redis::pipe() 35 | .atomic() 36 | .hincr(password, "upload", upload) 37 | .hincr(password, "download", download) 38 | .query_async(&mut client) 39 | .await 40 | .context("Executing command MULTI failed.")?) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use futures::{Sink, Stream}; 3 | use std::{io, pin::Pin}; 4 | use tokio::io::{AsyncRead, AsyncWrite}; 5 | 6 | pub trait AsyncStream: AsyncRead + AsyncWrite + Send {} 7 | impl AsyncStream for T {} 8 | 9 | pub type UdpPacket = (Bytes, String); 10 | 11 | pub trait UdpStream: 12 | Stream> + Sink + Send 13 | { 14 | } 15 | impl UdpStream for T where 16 | T: Stream> + Sink + Send 17 | { 18 | } 19 | 20 | pub type BoxedUdpStream = Pin>; 21 | 22 | pub type BoxedStream = Pin>; 23 | -------------------------------------------------------------------------------- /src/inbound/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod tls; 2 | pub mod trojan; 3 | 4 | use crate::common::{AsyncStream, UdpStream}; 5 | use async_trait::async_trait; 6 | use std::{io, net::SocketAddr}; 7 | 8 | pub enum InboundRequest { 9 | TcpConnect { addr: String, stream: T }, 10 | UdpBind { addr: String, stream: U }, 11 | } 12 | 13 | pub struct InboundAccept { 14 | pub metadata: Metadata, 15 | pub request: InboundRequest, 16 | } 17 | 18 | #[async_trait] 19 | pub trait Inbound: Send + Sync { 20 | type Metadata: Send + 'static; 21 | type TcpStream: AsyncStream + 'static; 22 | type UdpSocket: UdpStream + 'static; 23 | async fn accept( 24 | &self, 25 | stream: AcceptedStream, 26 | addr: SocketAddr, 27 | ) -> io::Result>> 28 | where 29 | AcceptedStream: AsyncStream + Unpin + 'static; 30 | } 31 | -------------------------------------------------------------------------------- /src/inbound/tls.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | common::AsyncStream, 3 | utils::{config::Tls, wildcard_match}, 4 | }; 5 | use anyhow::{anyhow, bail, Context, Result}; 6 | use futures::TryFutureExt; 7 | use log::{debug, info, trace}; 8 | use openssl::ssl::{self, NameType, Ssl, SslContext}; 9 | use std::{collections::HashSet, env, iter::FromIterator, ops::Deref, path::Path, pin::Pin}; 10 | use tokio::{fs::OpenOptions, io::AsyncWriteExt, sync::mpsc}; 11 | use tokio_openssl::SslStream; 12 | 13 | pub struct TlsContext { 14 | inner: ssl::SslContext, 15 | sni: Option>, 16 | } 17 | 18 | pub struct TlsAccept { 19 | pub sni_matched: bool, 20 | pub stream: SslStream, 21 | } 22 | 23 | fn get_alt_names_from_ssl_context(context: &SslContext) -> Option> { 24 | if let Some(cert) = context.certificate() { 25 | if let Some(names) = cert.subject_alt_names() { 26 | return Some( 27 | names 28 | .iter() 29 | .filter_map(|x| x.dnsname()) 30 | .map(String::from) 31 | .collect(), 32 | ); 33 | } 34 | } 35 | None 36 | } 37 | 38 | impl TlsContext { 39 | pub fn new(config: &Tls) -> Result { 40 | let (tx, rx) = mpsc::unbounded_channel::(); 41 | 42 | let keylog_callback = move |_: &ssl::SslRef, s: &str| { 43 | trace!("Keylog: {}", &s); 44 | if tx.is_closed() { 45 | return; 46 | } 47 | tx.send(String::from(s)).ok(); 48 | }; 49 | 50 | tokio::spawn(keylogger(rx).inspect_err(|e| log::error!("keylogger error: {:?}", e))); 51 | 52 | let mut acceptor = ssl::SslAcceptor::mozilla_modern_v5(ssl::SslMethod::tls_server())?; 53 | acceptor.set_verify(ssl::SslVerifyMode::NONE); 54 | acceptor.set_certificate_chain_file(&config.cert)?; 55 | acceptor.set_private_key_file(&config.key, ssl::SslFiletype::PEM)?; 56 | acceptor.check_private_key()?; 57 | acceptor.set_keylog_callback(keylog_callback); 58 | let context = acceptor.build().into_context(); 59 | 60 | let names_from_cert = get_alt_names_from_ssl_context(&context) 61 | .ok_or(anyhow!("Cannot get domain names from cert."))?; 62 | 63 | let names_from_config = config.sni.clone(); 64 | 65 | let sni = if names_from_config.len() == 0 { 66 | info!("Using SAN from cert: {:?}", &names_from_cert); 67 | None 68 | } else { 69 | for name in &names_from_config { 70 | if !wildcard_match::has_match(name, names_from_cert.iter()) { 71 | bail!("SNI {} in config not present in cert.", &name) 72 | } 73 | } 74 | Some(HashSet::from_iter(names_from_config)) 75 | }; 76 | 77 | Ok(TlsContext { 78 | inner: context, 79 | sni, 80 | }) 81 | } 82 | 83 | pub async fn accept(&self, stream: S) -> Result> { 84 | let mut stream = SslStream::new(Ssl::new(&self.inner)?, stream)?; 85 | Pin::new(&mut stream) 86 | .accept() 87 | .await 88 | .context("Invalid TLS connection.")?; 89 | let servername = stream 90 | .ssl() 91 | .servername(NameType::HOST_NAME) 92 | .unwrap_or_default(); 93 | debug!("SNI: {:?}", &servername); 94 | 95 | Ok(TlsAccept { 96 | sni_matched: if let Some(sni) = &self.sni { 97 | if sni.contains(servername) { 98 | true 99 | } else { 100 | wildcard_match::has_match(servername, sni.iter()) 101 | } 102 | } else { 103 | true 104 | }, 105 | stream, 106 | }) 107 | } 108 | } 109 | 110 | impl Deref for TlsContext { 111 | type Target = ssl::SslContext; 112 | 113 | fn deref(&self) -> &Self::Target { 114 | &self.inner 115 | } 116 | } 117 | 118 | async fn keylogger(mut rx: mpsc::UnboundedReceiver) -> Result<()> { 119 | let path = env::var("SSLKEYLOGFILE").unwrap_or_default(); 120 | if path == "" { 121 | return Ok(()); 122 | } 123 | let path = Path::new(&path); 124 | let mut keylogfile = OpenOptions::new() 125 | .append(true) 126 | .create(true) 127 | .open(path) 128 | .await 129 | .context("Cannot open keylog file.")?; 130 | loop { 131 | if let Some(keylog) = rx.recv().await { 132 | keylogfile.write_all(keylog.as_bytes()).await?; 133 | keylogfile.write_all(b"\n").await?; 134 | } else { 135 | break; 136 | } 137 | } 138 | 139 | Ok(()) 140 | } 141 | -------------------------------------------------------------------------------- /src/inbound/trojan.rs: -------------------------------------------------------------------------------- 1 | use super::{tls::TlsContext, Inbound, InboundAccept, InboundRequest}; 2 | use crate::{ 3 | auth::Auth, 4 | common::{AsyncStream, BoxedStream, BoxedUdpStream, UdpPacket}, 5 | inbound::tls::TlsAccept, 6 | utils::{config::Trojan, peekable_stream::PeekableStream}, 7 | }; 8 | use anyhow::{bail, Context, Result}; 9 | use async_trait::async_trait; 10 | use bytes::{Buf, BufMut, BytesMut}; 11 | use fallback::FallbackAcceptor; 12 | use log::{info, warn}; 13 | use socks5_protocol::{sync::FromIO, Address}; 14 | use std::{ 15 | io::{self, Cursor}, 16 | net::SocketAddr, 17 | str::FromStr, 18 | sync::Arc, 19 | }; 20 | use tokio::io::{AsyncRead, AsyncWrite}; 21 | use tokio_util::codec::{Decoder, Encoder, Framed}; 22 | 23 | mod fallback; 24 | 25 | const CMD: usize = 58; 26 | const ATYP: usize = 59; 27 | const DOMAIN_LEN: usize = 60; 28 | 29 | pub enum Cmd { 30 | Connect(String), 31 | UdpAssociate, 32 | } 33 | 34 | pub struct TrojanInbound { 35 | auth_hub: Arc, 36 | tls_context: TlsContext, 37 | fallback_acceptor: FallbackAcceptor, 38 | } 39 | 40 | fn map_err(e: anyhow::Error) -> io::Error { 41 | io::Error::new(io::ErrorKind::Other, e) 42 | } 43 | 44 | #[async_trait] 45 | impl Inbound for TrojanInbound { 46 | type Metadata = String; 47 | type TcpStream = BoxedStream; 48 | type UdpSocket = BoxedUdpStream; 49 | 50 | async fn accept( 51 | &self, 52 | stream: AcceptedStream, 53 | _addr: SocketAddr, 54 | ) -> io::Result>> 55 | where 56 | AcceptedStream: AsyncStream + Unpin + 'static, 57 | { 58 | let TlsAccept { 59 | stream, 60 | sni_matched, 61 | } = self.tls_context.accept(stream).await.map_err(map_err)?; 62 | 63 | if sni_matched { 64 | match self.accept2(stream).await { 65 | Ok(out) => return Ok(Some(out)), 66 | Err(stream) => { 67 | self.fallback_acceptor 68 | .accept(stream) 69 | .await 70 | .map_err(map_err)?; 71 | Ok(None) 72 | } 73 | } 74 | } else { 75 | warn!("Redirect to fallback: SNI mismatch."); 76 | self.fallback_acceptor 77 | .accept(stream) 78 | .await 79 | .map_err(map_err)?; 80 | Ok(None) 81 | } 82 | } 83 | } 84 | 85 | impl TrojanInbound { 86 | pub async fn new( 87 | auth: Arc, 88 | tls_context: TlsContext, 89 | config: Trojan, 90 | ) -> Result { 91 | let fallback_acceptor = FallbackAcceptor::new(config.fallback) 92 | .await 93 | .context("Failed to setup fallback server.")?; 94 | Ok(TrojanInbound { 95 | auth_hub: auth, 96 | tls_context, 97 | fallback_acceptor, 98 | }) 99 | } 100 | 101 | pub async fn accept2( 102 | &self, 103 | stream: IO, 104 | ) -> Result, PeekableStream> 105 | where 106 | IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, 107 | { 108 | let mut stream = PeekableStream::new(stream); 109 | match self.inner_accept(&mut stream).await { 110 | Ok((Cmd::Connect(addr), pw)) => Ok(InboundAccept { 111 | metadata: pw, 112 | request: InboundRequest::TcpConnect { 113 | addr, 114 | stream: Box::pin(stream), 115 | }, 116 | }), 117 | Ok((Cmd::UdpAssociate, pw)) => Ok(InboundAccept { 118 | metadata: pw, 119 | request: InboundRequest::UdpBind { 120 | addr: "0.0.0.0:0".to_string(), 121 | stream: Box::pin(Framed::new(stream, UdpCodec)), 122 | }, 123 | }), 124 | Err(e) => { 125 | warn!("Redirect to fallback: {:?}", e); 126 | return Err(stream); 127 | } 128 | } 129 | } 130 | 131 | async fn inner_accept(&self, stream: &mut PeekableStream) -> Result<(Cmd, String)> 132 | where 133 | IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, 134 | { 135 | let mut buf = vec![0u8; 56 + 2 + 2 + 1]; 136 | stream.peek_exact(&mut buf).await?; 137 | 138 | let password = String::from_utf8_lossy(&buf[0..56]); 139 | if let Err(_) = hex::decode(password.as_ref()) { 140 | bail!("Not trojan request.") 141 | } 142 | if !self.auth_hub.auth(&password).await? { 143 | bail!("{}", &password) 144 | } 145 | let password = password.to_string(); 146 | 147 | info!("Trojan request accepted: {}", &password); 148 | 149 | buf.resize(Self::calc_length(&buf)?, 0); 150 | stream.peek_exact(&mut buf).await?; 151 | 152 | let cmd = buf[CMD]; 153 | let mut reader = Cursor::new(buf); 154 | 155 | // read address 156 | reader.advance(ATYP); 157 | let address = Address::read_from(&mut reader)?; 158 | let end = reader.position() + 2; 159 | stream.drain(end as usize).await?; 160 | 161 | Ok(match cmd { 162 | 1 => (Cmd::Connect(address.to_string()), password), 163 | 3 => (Cmd::UdpAssociate, password), 164 | _ => bail!("Unknown command."), 165 | }) 166 | } 167 | 168 | // length of head must be 61 169 | fn calc_length(head: &[u8]) -> Result { 170 | let len = 171 | 60 + match head[ATYP] { 172 | // ipv4 173 | 1 => 8, 174 | // domain 175 | 3 => 1 + head[DOMAIN_LEN] + 2, 176 | // ipv6 177 | 4 => 18, 178 | _ => bail!("Unsupported atyp"), 179 | } + 2; 180 | Ok(len as usize) 181 | } 182 | } 183 | 184 | const UDP_MAX_SIZE: usize = 65535; 185 | // 259 is max size of address, atype 1 + domain len 1 + domain 255 + port 2 186 | const PREFIX_LENGTH: usize = 259 + 2 + 2; 187 | 188 | struct UdpCodec; 189 | 190 | impl Encoder for UdpCodec { 191 | type Error = io::Error; 192 | 193 | fn encode(&mut self, item: UdpPacket, dst: &mut BytesMut) -> Result<(), Self::Error> { 194 | if item.0.len() > UDP_MAX_SIZE { 195 | return Err(std::io::Error::new( 196 | std::io::ErrorKind::InvalidData, 197 | format!("Frame of length {} is too large.", item.0.len()), 198 | )); 199 | } 200 | 201 | dst.reserve(PREFIX_LENGTH + item.0.len()); 202 | let mut writer = dst.writer(); 203 | 204 | Address::from_str(&item.1) 205 | .map_err(|e| e.to_io_err())? 206 | .write_to(&mut writer) 207 | .map_err(|e| e.to_io_err())?; 208 | let dst = writer.into_inner(); 209 | 210 | dst.put_u16(item.0.len() as u16); 211 | dst.extend_from_slice(&[0x0D, 0x0A]); 212 | dst.extend_from_slice(&item.0); 213 | 214 | Ok(()) 215 | } 216 | } 217 | 218 | fn copy_2(b: &[u8]) -> [u8; 2] { 219 | let mut buf = [0u8; 2]; 220 | buf.copy_from_slice(&b); 221 | buf 222 | } 223 | 224 | impl Decoder for UdpCodec { 225 | type Item = UdpPacket; 226 | type Error = io::Error; 227 | 228 | fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { 229 | if src.len() < 2 { 230 | return Ok(None); 231 | } 232 | let head = copy_2(&src[0..2]); 233 | let addr_size = match head[0] { 234 | 1 => 7, 235 | 3 => 1 + head[1] as usize + 2, 236 | 4 => 19, 237 | _ => return Err(io::ErrorKind::InvalidData.into()), 238 | }; 239 | if src.len() < addr_size + 4 { 240 | return Ok(None); 241 | } 242 | let length = u16::from_be_bytes(copy_2(&src[addr_size..addr_size + 2])) as usize; 243 | if src.len() < addr_size + 4 + length { 244 | return Ok(None); 245 | } 246 | 247 | let mut reader = src.reader(); 248 | let address = Address::read_from(&mut reader).map_err(|e| e.to_io_err())?; 249 | let src = reader.into_inner(); 250 | 251 | // Length and CrLf 252 | src.get_u16(); 253 | src.get_u16(); 254 | 255 | let mut buf = vec![0u8; length as usize]; 256 | 257 | src.copy_to_slice(&mut buf); 258 | 259 | Ok(Some((buf.into(), address.to_string()))) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/inbound/trojan/fallback.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use http::{Method, Request, Response, StatusCode}; 3 | use hyper::{server::conn::Http, service::service_fn, Body}; 4 | use log::info; 5 | use tokio::{ 6 | io::{copy_bidirectional, AsyncRead, AsyncWrite}, 7 | net::TcpStream, 8 | }; 9 | 10 | pub struct FallbackAcceptor { 11 | // to a http server, if it's empty, fallback will use builtin http server. 12 | target: String, 13 | } 14 | 15 | impl FallbackAcceptor { 16 | pub async fn new(target: String) -> Result { 17 | if target.len() != 0 { 18 | TcpStream::connect(&target) 19 | .await 20 | .context(format!("Fallback server {} is unavailable.", &target))?; 21 | info!("Using fallback {}.", &target); 22 | } else { 23 | info!("Using built-in fallback server.") 24 | } 25 | Ok(FallbackAcceptor { target }) 26 | } 27 | pub async fn accept(&self, stream: IO) -> Result<()> 28 | where 29 | IO: AsyncRead + AsyncWrite + Unpin + 'static, 30 | { 31 | if self.target.len() == 0 { 32 | self.handle_builtin(stream).await 33 | } else { 34 | self.handle_forward(stream).await 35 | } 36 | } 37 | async fn handle_builtin(&self, stream: IO) -> Result<()> 38 | where 39 | IO: AsyncRead + AsyncWrite + Unpin + 'static, 40 | { 41 | Http::new() 42 | .http1_only(true) 43 | .http1_keep_alive(true) 44 | .serve_connection(stream, service_fn(hello)) 45 | .await 46 | .unwrap_or_else(|_| { 47 | info!("Fallback: Invalid HTTP method."); 48 | }); 49 | 50 | Ok(()) 51 | } 52 | async fn handle_forward(&self, mut stream: IO) -> Result<()> 53 | where 54 | IO: AsyncRead + AsyncWrite + Unpin, 55 | { 56 | let mut outbound = TcpStream::connect(&self.target) 57 | .await 58 | .context("Cannot connect to fallback.")?; 59 | 60 | copy_bidirectional(&mut outbound, &mut stream) 61 | .await 62 | .context("Cannot redirect stream to fallback.")?; 63 | 64 | Ok(()) 65 | } 66 | } 67 | 68 | async fn hello(req: Request) -> Result, http::Error> { 69 | if req.method() == Method::GET { 70 | Response::builder() 71 | .status(StatusCode::NOT_FOUND) 72 | .body(Body::empty()) 73 | } else { 74 | Response::builder() 75 | .status(StatusCode::METHOD_NOT_ALLOWED) 76 | .body(Body::empty()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(ip)] 2 | 3 | mod auth; 4 | mod common; 5 | mod inbound; 6 | mod outbound; 7 | mod relay; 8 | mod server; 9 | mod utils; 10 | 11 | use anyhow::{Context, Result}; 12 | use clap::Parser; 13 | use log::error; 14 | use utils::{config, logger}; 15 | 16 | #[derive(Parser, Debug)] 17 | #[command(version, author, about)] 18 | struct Opts { 19 | #[arg(short, long, default_value = "config.toml")] 20 | config: String, 21 | #[arg(long, default_value = "info", env = "LOGLEVEL")] 22 | log_level: String, 23 | } 24 | 25 | async fn start(config: &str) -> Result<()> { 26 | let config = config::load(config).context("Failed to parse config")?; 27 | server::start(config) 28 | .await 29 | .context("Failed to start service")?; 30 | Ok(()) 31 | } 32 | 33 | #[tokio::main] 34 | async fn main() -> Result<()> { 35 | let opts = Opts::parse(); 36 | logger::setup_logger(opts.log_level.as_str())?; 37 | 38 | if let Err(e) = start(opts.config.as_str()).await { 39 | error!("{:?}", e); 40 | } 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /src/outbound/direct.rs: -------------------------------------------------------------------------------- 1 | use std::{future::ready, io, sync::Arc}; 2 | 3 | use crate::{common::UdpPacket, utils::acl::ACL}; 4 | 5 | use super::{BoxedUdpStream, Outbound}; 6 | use anyhow::Result; 7 | use async_trait::async_trait; 8 | use bytes::{BufMut, Bytes, BytesMut}; 9 | use futures::{stream, FutureExt, SinkExt, StreamExt}; 10 | use log::{info, warn}; 11 | use tokio::net::{lookup_host, TcpStream, UdpSocket}; 12 | use tokio_util::{ 13 | codec::{Decoder, Encoder}, 14 | udp::UdpFramed, 15 | }; 16 | 17 | pub struct BytesCodec(()); 18 | 19 | impl BytesCodec { 20 | // Creates a new `BytesCodec` for shipping around raw bytes. 21 | pub fn new() -> BytesCodec { 22 | BytesCodec(()) 23 | } 24 | } 25 | 26 | impl Decoder for BytesCodec { 27 | type Item = Bytes; 28 | type Error = io::Error; 29 | 30 | fn decode(&mut self, buf: &mut BytesMut) -> Result, io::Error> { 31 | if !buf.is_empty() { 32 | let len = buf.len(); 33 | Ok(Some(buf.split_to(len).freeze())) 34 | } else { 35 | Ok(None) 36 | } 37 | } 38 | } 39 | 40 | impl Encoder for BytesCodec { 41 | type Error = io::Error; 42 | 43 | fn encode(&mut self, data: Bytes, buf: &mut BytesMut) -> Result<(), io::Error> { 44 | buf.reserve(data.len()); 45 | buf.put(data); 46 | Ok(()) 47 | } 48 | } 49 | 50 | pub struct DirectOutbound { 51 | acl: Arc, 52 | } 53 | 54 | impl DirectOutbound { 55 | pub fn new(acl: ACL) -> Self { 56 | DirectOutbound { acl: Arc::new(acl) } 57 | } 58 | } 59 | 60 | #[async_trait] 61 | impl Outbound for DirectOutbound { 62 | type TcpStream = TcpStream; 63 | type UdpSocket = BoxedUdpStream; 64 | 65 | async fn tcp_connect(&self, address: &str) -> io::Result { 66 | info!("Connecting to target {}", address); 67 | 68 | let addrs = lookup_host(address).await?.filter(|addr| { 69 | if self.acl.has_match(addr) { 70 | warn!("ACL blocked: {}", &addr); 71 | return false; 72 | } 73 | return true; 74 | }); 75 | 76 | let mut last_err = None; 77 | 78 | for addr in addrs { 79 | match TcpStream::connect(addr).await { 80 | Ok(stream) => return Ok(stream), 81 | Err(e) => last_err = Some(e), 82 | } 83 | } 84 | 85 | Err(last_err.unwrap_or_else(|| { 86 | io::Error::new( 87 | io::ErrorKind::InvalidInput, 88 | "could not resolve to any address", 89 | ) 90 | })) 91 | } 92 | 93 | async fn udp_bind(&self, address: &str) -> io::Result { 94 | let udp = UdpSocket::bind(address).await?; 95 | let acl = self.acl.clone(); 96 | let stream = UdpFramed::new(udp, BytesCodec::new()) 97 | .map(|r| r.map(|(a, b)| (a, b.to_string()))) 98 | .with_flat_map(move |(buf, addr): UdpPacket| { 99 | let acl = acl.clone(); 100 | stream::once(lookup_host(addr).map(move |r| { 101 | r.map(|mut i| i.next()) 102 | .ok() 103 | .flatten() 104 | .and_then(|i| (!acl.has_match(&i)).then(|| Ok((buf, i)))) 105 | })) 106 | .filter_map(|r| ready(r)) 107 | }); 108 | Ok(Box::pin(stream)) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/outbound/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod direct; 2 | 3 | use crate::common::{AsyncStream, BoxedUdpStream, UdpStream}; 4 | use async_trait::async_trait; 5 | use std::io; 6 | 7 | #[async_trait] 8 | pub trait Outbound: Send + Sync { 9 | type TcpStream: AsyncStream + 'static; 10 | type UdpSocket: UdpStream + 'static; 11 | 12 | async fn tcp_connect(&self, address: &str) -> io::Result; 13 | async fn udp_bind(&self, address: &str) -> io::Result; 14 | } 15 | -------------------------------------------------------------------------------- /src/relay.rs: -------------------------------------------------------------------------------- 1 | use crate::auth::Auth; 2 | use crate::common::UdpStream; 3 | use crate::inbound::{Inbound, InboundAccept, InboundRequest}; 4 | use crate::outbound::Outbound; 5 | use crate::utils::count_stream::CountStream; 6 | use anyhow::{anyhow, bail, Context, Error, Result}; 7 | use futures::{future::try_select, SinkExt, StreamExt, TryStreamExt}; 8 | use log::{error, info, warn}; 9 | use std::{io, sync::Arc, time::Duration}; 10 | use tokio::{ 11 | io::{copy, split, AsyncRead, AsyncWrite, AsyncWriteExt}, 12 | net::TcpListener, 13 | pin, select, 14 | sync::mpsc::{unbounded_channel, UnboundedReceiver}, 15 | time::timeout, 16 | }; 17 | use tokio_io_timeout::TimeoutStream; 18 | use tokio_stream::wrappers::UnboundedReceiverStream; 19 | 20 | const FULL_CONE_TIMEOUT: Duration = Duration::from_secs(30); 21 | 22 | pub struct Relay { 23 | listener: TcpListener, 24 | inbound: Arc, 25 | outbound: Arc, 26 | tcp_nodelay: bool, 27 | pub tcp_timeout: Option, 28 | } 29 | 30 | /// Connect two `TcpStream`. Unlike `copy_bidirectional`, it closes the other side once one side is done. 31 | pub async fn connect_tcp( 32 | t1: impl AsyncRead + AsyncWrite, 33 | t2: impl AsyncRead + AsyncWrite, 34 | ) -> io::Result<()> { 35 | let (mut read_1, mut write_1) = split(t1); 36 | let (mut read_2, mut write_2) = split(t2); 37 | 38 | let fut1 = async { 39 | let r = copy(&mut read_1, &mut write_2).await; 40 | write_2.shutdown().await?; 41 | r 42 | }; 43 | let fut2 = async { 44 | let r = copy(&mut read_2, &mut write_1).await; 45 | write_1.shutdown().await?; 46 | r 47 | }; 48 | 49 | pin!(fut1, fut2); 50 | 51 | match try_select(fut1, fut2).await { 52 | Ok(_) => {} 53 | Err(e) => return Err(e.factor_first().0), 54 | }; 55 | 56 | Ok(()) 57 | } 58 | 59 | impl Relay 60 | where 61 | I: Inbound + 'static, 62 | O: Outbound + 'static, 63 | { 64 | async fn process( 65 | outbound: Arc, 66 | inbound_accept: InboundAccept, 67 | ) -> Result<()> { 68 | match inbound_accept.request { 69 | InboundRequest::TcpConnect { addr, stream } => { 70 | let target = outbound.tcp_connect(&addr).await?; 71 | pin!(target, stream); 72 | connect_tcp(&mut stream, &mut target) 73 | .await 74 | .map_err(|e| anyhow!("Connection reset by peer. {:?}", e))?; 75 | } 76 | InboundRequest::UdpBind { addr, stream } => { 77 | let target = outbound.udp_bind(&addr).await?; 78 | pin!(target, stream); 79 | info!("UDP tunnel created."); 80 | handle_udp(&mut stream, &mut target).await?; 81 | stream.close().await?; 82 | target.close().await?; 83 | } 84 | } 85 | Ok(()) 86 | } 87 | } 88 | 89 | struct PacketStat { 90 | password: String, 91 | upload: u64, 92 | download: u64, 93 | } 94 | 95 | impl PacketStat { 96 | // conn_id, upload, download 97 | fn new(password: String, upload: u64, download: u64) -> Self { 98 | PacketStat { 99 | password, 100 | upload, 101 | download, 102 | } 103 | } 104 | } 105 | 106 | impl Relay 107 | where 108 | I: Inbound + 'static, 109 | O: Outbound + 'static, 110 | { 111 | pub fn new(listener: TcpListener, inbound: I, outbound: O, tcp_nodelay: bool) -> Self { 112 | Relay { 113 | listener, 114 | inbound: Arc::new(inbound), 115 | outbound: Arc::new(outbound), 116 | tcp_nodelay, 117 | tcp_timeout: None, 118 | } 119 | } 120 | pub async fn serve_trojan(&self, auth: Arc) -> Result<()> { 121 | let (tx, rx) = unbounded_channel::(); 122 | tokio::spawn(stat(auth, rx)); 123 | loop { 124 | let (stream, addr) = self.listener.accept().await?; 125 | info!("Inbound connection from {}", addr); 126 | stream 127 | .set_nodelay(self.tcp_nodelay) 128 | .context("Set TCP_NODELAY failed")?; 129 | 130 | let (stream, sender) = CountStream::new2(stream); 131 | let mut stream = TimeoutStream::new(stream); 132 | stream.set_read_timeout(self.tcp_timeout); 133 | 134 | let inbound = self.inbound.clone(); 135 | let outbound = self.outbound.clone(); 136 | let t = tx.clone(); 137 | tokio::spawn(async move { 138 | let inbound_accept = inbound.accept(Box::pin(stream), addr).await?; 139 | if let Some(accept) = inbound_accept { 140 | // Here we got trojan password 141 | 142 | let password = accept.metadata.clone(); 143 | sender 144 | .send(Box::new(move |read: usize, write: usize| { 145 | t.send(PacketStat::new(password, read as u64, write as u64)) 146 | .ok(); 147 | })) 148 | .ok(); 149 | if let Err(e) = Self::process(outbound, accept).await { 150 | warn!("Relay error: {:?}", e) 151 | } 152 | } 153 | Ok(()) as Result<()> 154 | }); 155 | } 156 | } 157 | } 158 | 159 | async fn stat(auth: Arc, rx: UnboundedReceiver) { 160 | let stream = UnboundedReceiverStream::new(rx); 161 | stream 162 | .for_each_concurrent(10, |i| { 163 | let auth = auth.clone(); 164 | async move { 165 | if let Err(e) = auth.stat(&i.password, i.upload, i.download).await { 166 | error!("Failed to stat: {:?}", e); 167 | } 168 | } 169 | }) 170 | .await; 171 | } 172 | 173 | async fn handle_udp(incoming: impl UdpStream, target: impl UdpStream) -> Result<()> { 174 | let (mut at, mut ar) = incoming.split(); 175 | let (mut bt, mut br) = target.split(); 176 | 177 | let inbound = async { 178 | loop { 179 | let (buf, addr) = match br.try_next().await { 180 | Ok(Some(r)) => r, 181 | _ => continue, 182 | }; 183 | at.send((buf, addr)) 184 | .await 185 | .map_err(|_| anyhow!("Broken pipe."))?; 186 | } 187 | #[allow(unreachable_code)] 188 | Ok::<(), Error>(()) 189 | }; 190 | 191 | let outbound = async { 192 | while let Ok(Some(res)) = timeout(FULL_CONE_TIMEOUT, ar.next()).await { 193 | match res { 194 | Ok((buf, addr)) => { 195 | if let Err(e) = bt.send((buf, addr.clone())).await { 196 | warn!("Unable to send to target {}: {}", addr, e); 197 | }; 198 | } 199 | Err(_) => bail!("Connection reset by peer."), 200 | } 201 | } 202 | Ok::<(), Error>(()) 203 | }; 204 | 205 | select! { 206 | r = inbound => r?, 207 | r = outbound => r?, 208 | }; 209 | info!("UDP tunnel closed."); 210 | Ok(()) 211 | } 212 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use crate::{ 4 | auth::{Auth, AuthHub}, 5 | inbound::{tls::TlsContext, trojan::TrojanInbound}, 6 | outbound::direct::DirectOutbound, 7 | relay::Relay, 8 | utils::{acl::ACL, config::Config}, 9 | }; 10 | use anyhow::{Context, Result}; 11 | use log::{debug, info}; 12 | use tokio::net::TcpListener; 13 | 14 | pub async fn start(config: Config) -> Result<()> { 15 | debug!("Loading Config: {:?}", &config); 16 | 17 | let listener = TcpListener::bind(&config.tls.listen.as_str()) 18 | .await 19 | .context(format!("Failed to bind address {}", &config.tls.listen))?; 20 | 21 | let auth_hub: Arc = Arc::new(AuthHub::new(&config).await?); 22 | let tls_context = TlsContext::new(&config.tls).context("Failed to setup TLS server.")?; 23 | let inbound = TrojanInbound::new(auth_hub.clone(), tls_context, config.trojan).await?; 24 | let outbound = DirectOutbound::new(ACL::new(config.outbound.block_local)); 25 | 26 | let mut relay = Relay::new(listener, inbound, outbound, config.tls.tcp_nodelay); 27 | relay.tcp_timeout = Some(Duration::from_secs(600)); 28 | 29 | info!("Service started."); 30 | 31 | relay.serve_trojan(auth_hub).await?; 32 | 33 | info!("Service stopped."); 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/acl.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | pub struct ACL { 4 | block_local: bool, 5 | } 6 | 7 | impl ACL { 8 | pub fn new(block_local: bool) -> Self { 9 | ACL { block_local } 10 | } 11 | 12 | pub fn has_match(&self, address: &SocketAddr) -> bool { 13 | if !self.block_local { 14 | return false; 15 | } 16 | match address { 17 | SocketAddr::V4(addr) => !addr.ip().is_global(), 18 | SocketAddr::V6(addr) => !addr.ip().is_global(), 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use serde_derive::Deserialize; 3 | use serde_with::formats::PreferMany; 4 | use serde_with::{serde_as, OneOrMany}; 5 | 6 | #[derive(Deserialize, Debug)] 7 | #[serde(rename_all = "lowercase")] 8 | pub enum Mode { 9 | Server, 10 | } 11 | 12 | impl Default for Mode { 13 | fn default() -> Self { 14 | Mode::Server 15 | } 16 | } 17 | 18 | #[derive(Deserialize, Debug)] 19 | pub struct Config { 20 | #[serde(default)] 21 | pub mode: Mode, 22 | #[serde(default)] 23 | pub trojan: Trojan, 24 | pub tls: Tls, 25 | #[serde(default)] 26 | pub outbound: Outbound, 27 | pub redis: Option, 28 | } 29 | 30 | #[derive(Deserialize, Debug, Default)] 31 | pub struct Trojan { 32 | #[serde(default)] 33 | pub password: Vec, 34 | #[serde(default)] 35 | pub fallback: String, 36 | } 37 | 38 | fn default_listen() -> String { 39 | String::from("0.0.0.0:443") 40 | } 41 | 42 | #[serde_as] 43 | #[derive(Deserialize, Debug)] 44 | pub struct Tls { 45 | #[serde(default = "default_listen")] 46 | pub listen: String, 47 | #[serde(default)] 48 | pub tcp_nodelay: bool, 49 | #[serde(default)] 50 | #[serde_as(deserialize_as = "OneOrMany<_, PreferMany>")] 51 | pub sni: Vec, 52 | pub cert: String, 53 | pub key: String, 54 | } 55 | 56 | #[derive(Deserialize, Debug, Default)] 57 | pub struct Outbound { 58 | #[serde(default)] 59 | pub block_local: bool, 60 | } 61 | 62 | fn default_redis() -> String { 63 | String::from("127.0.0.1:6379") 64 | } 65 | 66 | #[derive(Deserialize, Debug, Clone)] 67 | pub struct Redis { 68 | #[serde(default = "default_redis")] 69 | pub server: String, 70 | } 71 | 72 | pub fn load(s: &str) -> Result { 73 | let config_string = std::fs::read_to_string(s)?; 74 | let config = toml::from_str(&config_string)?; 75 | 76 | Ok(config) 77 | } 78 | -------------------------------------------------------------------------------- /src/utils/count_stream.rs: -------------------------------------------------------------------------------- 1 | use futures::{channel::oneshot, ready}; 2 | use std::{ 3 | io, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 8 | 9 | pub type Callback = Box; 10 | pub struct CountStream { 11 | inner: S, 12 | read: usize, 13 | write: usize, 14 | on_drop: oneshot::Receiver, 15 | } 16 | 17 | impl Drop for CountStream { 18 | fn drop(&mut self) { 19 | if let Ok(Some(f)) = self.on_drop.try_recv() { 20 | f(self.read, self.write) 21 | } 22 | } 23 | } 24 | 25 | impl AsyncRead for CountStream 26 | where 27 | S: AsyncRead + Unpin, 28 | { 29 | fn poll_read( 30 | mut self: Pin<&mut Self>, 31 | cx: &mut Context<'_>, 32 | buf: &mut ReadBuf, 33 | ) -> Poll> { 34 | let before = buf.filled().len(); 35 | 36 | ready!(Pin::new(&mut self.inner).poll_read(cx, buf))?; 37 | 38 | let read = &buf.filled()[before..]; 39 | self.read += read.len(); 40 | 41 | Poll::Ready(Ok(())) 42 | } 43 | } 44 | 45 | impl AsyncWrite for CountStream 46 | where 47 | S: AsyncWrite + Unpin, 48 | { 49 | fn poll_write( 50 | mut self: Pin<&mut Self>, 51 | cx: &mut Context<'_>, 52 | buf: &[u8], 53 | ) -> Poll> { 54 | let result = ready!(Pin::new(&mut self.inner).poll_write(cx, buf))?; 55 | 56 | self.write += result; 57 | 58 | Poll::Ready(Ok(result)) 59 | } 60 | 61 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 62 | Pin::new(&mut self.inner).poll_flush(cx) 63 | } 64 | 65 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 66 | Pin::new(&mut self.inner).poll_shutdown(cx) 67 | } 68 | } 69 | 70 | impl CountStream { 71 | pub fn new2(inner: S) -> (Self, oneshot::Sender) { 72 | let (tx, rx) = oneshot::channel(); 73 | ( 74 | CountStream { 75 | inner, 76 | read: 0, 77 | write: 0, 78 | on_drop: rx, 79 | }, 80 | tx, 81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/utils/logger.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use fern::colors::{Color, ColoredLevelConfig}; 3 | use log::{info, LevelFilter}; 4 | use std::str::FromStr; 5 | 6 | pub fn setup_logger(log_level: &str) -> Result<()> { 7 | let loglevel = LevelFilter::from_str(log_level).unwrap_or_else(|err| { 8 | eprintln!("Error parsing log_level: {}", err); 9 | LevelFilter::Info 10 | }); 11 | 12 | let colors = ColoredLevelConfig::new() 13 | .error(Color::Red) 14 | .warn(Color::Yellow) 15 | .trace(Color::BrightBlack); 16 | 17 | fern::Dispatch::new() 18 | .format(move |out, message, record| { 19 | out.finish(format_args!( 20 | "{}[{}] {}", 21 | chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), 22 | colors.color(record.level()), 23 | message 24 | )) 25 | }) 26 | .level(loglevel) 27 | .chain(std::io::stdout()) 28 | .apply()?; 29 | 30 | info!("log_level={}", loglevel); 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod acl; 2 | pub mod config; 3 | pub mod count_stream; 4 | pub mod logger; 5 | pub mod peekable_stream; 6 | pub mod wildcard_match; 7 | -------------------------------------------------------------------------------- /src/utils/peekable_stream.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::{ 4 | collections::VecDeque, 5 | io, 6 | pin::Pin, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, ReadBuf}; 11 | 12 | #[derive(Debug)] 13 | pub struct PeekableStream { 14 | inner: S, 15 | buf: VecDeque, 16 | } 17 | 18 | impl AsyncRead for PeekableStream 19 | where 20 | S: AsyncRead + Unpin, 21 | { 22 | fn poll_read( 23 | mut self: Pin<&mut Self>, 24 | cx: &mut Context<'_>, 25 | buf: &mut ReadBuf, 26 | ) -> Poll> { 27 | let (first, ..) = &self.buf.as_slices(); 28 | if first.len() > 0 { 29 | let read = first.len().min(buf.remaining()); 30 | let unfilled = buf.initialize_unfilled_to(read); 31 | unfilled[0..read].copy_from_slice(&first[0..read]); 32 | buf.advance(read); 33 | 34 | // remove 0..read 35 | self.buf.drain(0..read); 36 | 37 | Poll::Ready(Ok(())) 38 | } else { 39 | Pin::new(&mut self.inner).poll_read(cx, buf) 40 | } 41 | } 42 | } 43 | 44 | impl AsyncWrite for PeekableStream 45 | where 46 | S: AsyncWrite + Unpin, 47 | { 48 | fn poll_write( 49 | mut self: Pin<&mut Self>, 50 | cx: &mut Context<'_>, 51 | buf: &[u8], 52 | ) -> Poll> { 53 | Pin::new(&mut self.inner).poll_write(cx, buf) 54 | } 55 | 56 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 57 | Pin::new(&mut self.inner).poll_flush(cx) 58 | } 59 | 60 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 61 | Pin::new(&mut self.inner).poll_shutdown(cx) 62 | } 63 | } 64 | 65 | impl PeekableStream { 66 | pub fn new(inner: S) -> Self { 67 | PeekableStream { 68 | inner, 69 | buf: VecDeque::new(), 70 | } 71 | } 72 | pub fn with_buf(inner: S, buf: VecDeque) -> Self { 73 | PeekableStream { inner, buf } 74 | } 75 | pub fn into_inner(self) -> (S, VecDeque) { 76 | (self.inner, self.buf) 77 | } 78 | } 79 | 80 | impl PeekableStream 81 | where 82 | S: AsyncRead + Unpin, 83 | { 84 | // Fill self.buf to size using self.tcp.read_exact 85 | async fn fill_buf(&mut self, size: usize) -> io::Result<()> { 86 | if size > self.buf.len() { 87 | let to_read = size - self.buf.len(); 88 | let mut buf = vec![0u8; to_read]; 89 | self.inner.read_exact(&mut buf).await?; 90 | self.buf.append(&mut buf.into()); 91 | } 92 | Ok(()) 93 | } 94 | pub async fn peek_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { 95 | self.fill_buf(buf.len()).await?; 96 | let self_buf = self.buf.make_contiguous(); 97 | buf.copy_from_slice(&self_buf[0..buf.len()]); 98 | 99 | Ok(()) 100 | } 101 | pub async fn drain(&mut self, size: usize) -> io::Result<()> { 102 | self.fill_buf(size).await?; 103 | self.buf.drain(0..size); 104 | Ok(()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/utils/wildcard_match.rs: -------------------------------------------------------------------------------- 1 | fn is_match(input: &str, pattern: &str) -> bool { 2 | let mut input = input.chars().rev().map(|c| c.to_ascii_lowercase()); 3 | let mut pattern = pattern.chars().rev().map(|c| c.to_ascii_lowercase()); 4 | 5 | let mut is_wildcard = false; 6 | 7 | while let Some(p) = pattern.next() { 8 | if let Some(i) = input.next() { 9 | if i == p { 10 | continue; 11 | } else if p == '*' { 12 | if i == '.' { 13 | return false; 14 | } 15 | is_wildcard = true; 16 | break; 17 | } else { 18 | return false; 19 | } 20 | } else { 21 | return false; 22 | } 23 | } 24 | while let Some(i) = input.next() { 25 | if !is_wildcard { 26 | return false; 27 | } else { 28 | if i == '.' { 29 | return false; 30 | } 31 | } 32 | } 33 | true 34 | } 35 | 36 | pub fn has_match<'a>(input: &'a str, pattern_iter: impl Iterator) -> bool { 37 | for pattern in pattern_iter { 38 | if is_match(input, pattern) { 39 | return true; 40 | } 41 | } 42 | false 43 | } 44 | --------------------------------------------------------------------------------