├── .github └── workflows │ ├── publish-binaries.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README-EN.md ├── README.md ├── example-config.yaml ├── src ├── config.rs ├── main.rs ├── plugins │ ├── kcp │ │ ├── config.rs │ │ ├── listener.rs │ │ ├── mod.rs │ │ ├── session.rs │ │ ├── skcp.rs │ │ ├── stream.rs │ │ └── utils.rs │ └── mod.rs └── servers │ ├── mod.rs │ └── protocol │ ├── kcp.rs │ ├── mod.rs │ ├── tcp.rs │ └── tls.rs └── tests └── config.yaml /.github/workflows/publish-binaries.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | 5 | name: Publish binaries to release 6 | 7 | jobs: 8 | publish: 9 | name: Publish for ${{ matrix.os }} 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | include: 16 | - os: ubuntu-latest 17 | artifact_name: fourth 18 | asset_name: fourth-linux-amd64 19 | - os: macos-latest 20 | artifact_name: fourth 21 | asset_name: fourth-macos-amd64 22 | - os: windows-latest 23 | artifact_name: fourth.exe 24 | asset_name: fourth-windows-amd64.exe 25 | 26 | steps: 27 | - uses: hecrj/setup-rust-action@master 28 | with: 29 | rust-version: stable 30 | - uses: actions/checkout@v2 31 | - name: Build 32 | run: cargo build --release --locked 33 | - name: Publish 34 | uses: svenstaro/upload-release-action@v1-release 35 | with: 36 | repo_token: ${{ secrets.PUBLISH_TOKEN }} 37 | file: target/release/${{ matrix.artifact_name }} 38 | asset_name: ${{ matrix.asset_name }} 39 | tag: ${{ github.ref }} 40 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Upgrade Rust 20 | run: rustup update 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.0.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.2.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 36 | 37 | [[package]] 38 | name = "byte_string" 39 | version = "1.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "11aade7a05aa8c3a351cedc44c3fc45806430543382fcc4743a9b757a2a0b4ed" 42 | 43 | [[package]] 44 | name = "byteorder" 45 | version = "1.4.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 48 | 49 | [[package]] 50 | name = "bytes" 51 | version = "0.4.12" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 54 | dependencies = [ 55 | "byteorder", 56 | "iovec", 57 | ] 58 | 59 | [[package]] 60 | name = "bytes" 61 | version = "1.1.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 64 | 65 | [[package]] 66 | name = "cfg-if" 67 | version = "1.0.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 70 | 71 | [[package]] 72 | name = "dtoa" 73 | version = "0.4.8" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" 76 | 77 | [[package]] 78 | name = "enum_primitive" 79 | version = "0.1.1" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" 82 | dependencies = [ 83 | "num-traits 0.1.43", 84 | ] 85 | 86 | [[package]] 87 | name = "env_logger" 88 | version = "0.7.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 91 | dependencies = [ 92 | "atty", 93 | "humantime", 94 | "log 0.4.14", 95 | "regex", 96 | "termcolor", 97 | ] 98 | 99 | [[package]] 100 | name = "form_urlencoded" 101 | version = "1.0.1" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 104 | dependencies = [ 105 | "matches", 106 | "percent-encoding", 107 | ] 108 | 109 | [[package]] 110 | name = "fourth" 111 | version = "0.1.5" 112 | dependencies = [ 113 | "byte_string", 114 | "bytes 1.1.0", 115 | "futures", 116 | "kcp", 117 | "log 0.4.14", 118 | "pretty_env_logger", 119 | "serde", 120 | "serde_yaml", 121 | "tls-parser", 122 | "tokio", 123 | "url", 124 | ] 125 | 126 | [[package]] 127 | name = "futures" 128 | version = "0.3.17" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" 131 | dependencies = [ 132 | "futures-channel", 133 | "futures-core", 134 | "futures-executor", 135 | "futures-io", 136 | "futures-sink", 137 | "futures-task", 138 | "futures-util", 139 | ] 140 | 141 | [[package]] 142 | name = "futures-channel" 143 | version = "0.3.17" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" 146 | dependencies = [ 147 | "futures-core", 148 | "futures-sink", 149 | ] 150 | 151 | [[package]] 152 | name = "futures-core" 153 | version = "0.3.17" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" 156 | 157 | [[package]] 158 | name = "futures-executor" 159 | version = "0.3.17" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" 162 | dependencies = [ 163 | "futures-core", 164 | "futures-task", 165 | "futures-util", 166 | ] 167 | 168 | [[package]] 169 | name = "futures-io" 170 | version = "0.3.17" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" 173 | 174 | [[package]] 175 | name = "futures-macro" 176 | version = "0.3.17" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" 179 | dependencies = [ 180 | "autocfg", 181 | "proc-macro-hack", 182 | "proc-macro2", 183 | "quote", 184 | "syn", 185 | ] 186 | 187 | [[package]] 188 | name = "futures-sink" 189 | version = "0.3.17" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" 192 | 193 | [[package]] 194 | name = "futures-task" 195 | version = "0.3.17" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" 198 | 199 | [[package]] 200 | name = "futures-util" 201 | version = "0.3.17" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" 204 | dependencies = [ 205 | "autocfg", 206 | "futures-channel", 207 | "futures-core", 208 | "futures-io", 209 | "futures-macro", 210 | "futures-sink", 211 | "futures-task", 212 | "memchr", 213 | "pin-project-lite", 214 | "pin-utils", 215 | "proc-macro-hack", 216 | "proc-macro-nested", 217 | "slab", 218 | ] 219 | 220 | [[package]] 221 | name = "getrandom" 222 | version = "0.2.3" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 225 | dependencies = [ 226 | "cfg-if", 227 | "libc", 228 | "wasi", 229 | ] 230 | 231 | [[package]] 232 | name = "hashbrown" 233 | version = "0.11.2" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 236 | 237 | [[package]] 238 | name = "hermit-abi" 239 | version = "0.1.19" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 242 | dependencies = [ 243 | "libc", 244 | ] 245 | 246 | [[package]] 247 | name = "humantime" 248 | version = "1.3.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 251 | dependencies = [ 252 | "quick-error", 253 | ] 254 | 255 | [[package]] 256 | name = "idna" 257 | version = "0.2.3" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 260 | dependencies = [ 261 | "matches", 262 | "unicode-bidi", 263 | "unicode-normalization", 264 | ] 265 | 266 | [[package]] 267 | name = "indexmap" 268 | version = "1.7.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 271 | dependencies = [ 272 | "autocfg", 273 | "hashbrown", 274 | ] 275 | 276 | [[package]] 277 | name = "instant" 278 | version = "0.1.11" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" 281 | dependencies = [ 282 | "cfg-if", 283 | ] 284 | 285 | [[package]] 286 | name = "iovec" 287 | version = "0.1.4" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 290 | dependencies = [ 291 | "libc", 292 | ] 293 | 294 | [[package]] 295 | name = "kcp" 296 | version = "0.4.10" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "09481e52ffe09d417d8b770217faca77eeb048ab5f337562cede72070fc91b21" 299 | dependencies = [ 300 | "bytes 0.4.12", 301 | "log 0.3.9", 302 | ] 303 | 304 | [[package]] 305 | name = "libc" 306 | version = "0.2.103" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" 309 | 310 | [[package]] 311 | name = "linked-hash-map" 312 | version = "0.5.4" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 315 | 316 | [[package]] 317 | name = "lock_api" 318 | version = "0.4.5" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" 321 | dependencies = [ 322 | "scopeguard", 323 | ] 324 | 325 | [[package]] 326 | name = "log" 327 | version = "0.3.9" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 330 | dependencies = [ 331 | "log 0.4.14", 332 | ] 333 | 334 | [[package]] 335 | name = "log" 336 | version = "0.4.14" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 339 | dependencies = [ 340 | "cfg-if", 341 | ] 342 | 343 | [[package]] 344 | name = "matches" 345 | version = "0.1.9" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 348 | 349 | [[package]] 350 | name = "memchr" 351 | version = "2.4.1" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 354 | 355 | [[package]] 356 | name = "minimal-lexical" 357 | version = "0.1.4" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677" 360 | 361 | [[package]] 362 | name = "mio" 363 | version = "0.7.14" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 366 | dependencies = [ 367 | "libc", 368 | "log 0.4.14", 369 | "miow", 370 | "ntapi", 371 | "winapi", 372 | ] 373 | 374 | [[package]] 375 | name = "miow" 376 | version = "0.3.7" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 379 | dependencies = [ 380 | "winapi", 381 | ] 382 | 383 | [[package]] 384 | name = "nom" 385 | version = "7.0.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1" 388 | dependencies = [ 389 | "memchr", 390 | "minimal-lexical", 391 | "version_check", 392 | ] 393 | 394 | [[package]] 395 | name = "nom-derive" 396 | version = "0.10.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "007e042d4414938f8bed4f97b8e358b215e06c5aa80ce237decab36807aa95eb" 399 | dependencies = [ 400 | "nom", 401 | "nom-derive-impl", 402 | "rustversion", 403 | ] 404 | 405 | [[package]] 406 | name = "nom-derive-impl" 407 | version = "0.10.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "770ac067d17c285c04d84ca061aa439e269c053a413d36796e2d8ab7f481375d" 410 | dependencies = [ 411 | "proc-macro2", 412 | "quote", 413 | "syn", 414 | ] 415 | 416 | [[package]] 417 | name = "ntapi" 418 | version = "0.3.6" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 421 | dependencies = [ 422 | "winapi", 423 | ] 424 | 425 | [[package]] 426 | name = "num-traits" 427 | version = "0.1.43" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" 430 | dependencies = [ 431 | "num-traits 0.2.14", 432 | ] 433 | 434 | [[package]] 435 | name = "num-traits" 436 | version = "0.2.14" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 439 | dependencies = [ 440 | "autocfg", 441 | ] 442 | 443 | [[package]] 444 | name = "num_cpus" 445 | version = "1.13.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 448 | dependencies = [ 449 | "hermit-abi", 450 | "libc", 451 | ] 452 | 453 | [[package]] 454 | name = "once_cell" 455 | version = "1.8.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 458 | 459 | [[package]] 460 | name = "parking_lot" 461 | version = "0.11.2" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 464 | dependencies = [ 465 | "instant", 466 | "lock_api", 467 | "parking_lot_core", 468 | ] 469 | 470 | [[package]] 471 | name = "parking_lot_core" 472 | version = "0.8.5" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 475 | dependencies = [ 476 | "cfg-if", 477 | "instant", 478 | "libc", 479 | "redox_syscall", 480 | "smallvec", 481 | "winapi", 482 | ] 483 | 484 | [[package]] 485 | name = "percent-encoding" 486 | version = "2.1.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 489 | 490 | [[package]] 491 | name = "phf" 492 | version = "0.10.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f" 495 | dependencies = [ 496 | "phf_shared", 497 | ] 498 | 499 | [[package]] 500 | name = "phf_codegen" 501 | version = "0.10.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 504 | dependencies = [ 505 | "phf_generator", 506 | "phf_shared", 507 | ] 508 | 509 | [[package]] 510 | name = "phf_generator" 511 | version = "0.10.0" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 514 | dependencies = [ 515 | "phf_shared", 516 | "rand", 517 | ] 518 | 519 | [[package]] 520 | name = "phf_shared" 521 | version = "0.10.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 524 | dependencies = [ 525 | "siphasher", 526 | ] 527 | 528 | [[package]] 529 | name = "pin-project-lite" 530 | version = "0.2.7" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 533 | 534 | [[package]] 535 | name = "pin-utils" 536 | version = "0.1.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 539 | 540 | [[package]] 541 | name = "ppv-lite86" 542 | version = "0.2.14" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" 545 | 546 | [[package]] 547 | name = "pretty_env_logger" 548 | version = "0.4.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" 551 | dependencies = [ 552 | "env_logger", 553 | "log 0.4.14", 554 | ] 555 | 556 | [[package]] 557 | name = "proc-macro-hack" 558 | version = "0.5.19" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 561 | 562 | [[package]] 563 | name = "proc-macro-nested" 564 | version = "0.1.7" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 567 | 568 | [[package]] 569 | name = "proc-macro2" 570 | version = "1.0.29" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 573 | dependencies = [ 574 | "unicode-xid", 575 | ] 576 | 577 | [[package]] 578 | name = "quick-error" 579 | version = "1.2.3" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 582 | 583 | [[package]] 584 | name = "quote" 585 | version = "1.0.10" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 588 | dependencies = [ 589 | "proc-macro2", 590 | ] 591 | 592 | [[package]] 593 | name = "rand" 594 | version = "0.8.4" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 597 | dependencies = [ 598 | "libc", 599 | "rand_chacha", 600 | "rand_core", 601 | "rand_hc", 602 | ] 603 | 604 | [[package]] 605 | name = "rand_chacha" 606 | version = "0.3.1" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 609 | dependencies = [ 610 | "ppv-lite86", 611 | "rand_core", 612 | ] 613 | 614 | [[package]] 615 | name = "rand_core" 616 | version = "0.6.3" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 619 | dependencies = [ 620 | "getrandom", 621 | ] 622 | 623 | [[package]] 624 | name = "rand_hc" 625 | version = "0.3.1" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 628 | dependencies = [ 629 | "rand_core", 630 | ] 631 | 632 | [[package]] 633 | name = "redox_syscall" 634 | version = "0.2.10" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 637 | dependencies = [ 638 | "bitflags", 639 | ] 640 | 641 | [[package]] 642 | name = "regex" 643 | version = "1.5.4" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 646 | dependencies = [ 647 | "aho-corasick", 648 | "memchr", 649 | "regex-syntax", 650 | ] 651 | 652 | [[package]] 653 | name = "regex-syntax" 654 | version = "0.6.25" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 657 | 658 | [[package]] 659 | name = "rusticata-macros" 660 | version = "4.0.0" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "65c52377bb2288aa522a0c8208947fada1e0c76397f108cc08f57efe6077b50d" 663 | dependencies = [ 664 | "nom", 665 | ] 666 | 667 | [[package]] 668 | name = "rustversion" 669 | version = "1.0.5" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" 672 | 673 | [[package]] 674 | name = "scopeguard" 675 | version = "1.1.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 678 | 679 | [[package]] 680 | name = "serde" 681 | version = "1.0.130" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 684 | dependencies = [ 685 | "serde_derive", 686 | ] 687 | 688 | [[package]] 689 | name = "serde_derive" 690 | version = "1.0.130" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 693 | dependencies = [ 694 | "proc-macro2", 695 | "quote", 696 | "syn", 697 | ] 698 | 699 | [[package]] 700 | name = "serde_yaml" 701 | version = "0.8.21" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" 704 | dependencies = [ 705 | "dtoa", 706 | "indexmap", 707 | "serde", 708 | "yaml-rust", 709 | ] 710 | 711 | [[package]] 712 | name = "signal-hook-registry" 713 | version = "1.4.0" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 716 | dependencies = [ 717 | "libc", 718 | ] 719 | 720 | [[package]] 721 | name = "siphasher" 722 | version = "0.3.7" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 725 | 726 | [[package]] 727 | name = "slab" 728 | version = "0.4.5" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 731 | 732 | [[package]] 733 | name = "smallvec" 734 | version = "1.7.0" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 737 | 738 | [[package]] 739 | name = "syn" 740 | version = "1.0.80" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" 743 | dependencies = [ 744 | "proc-macro2", 745 | "quote", 746 | "unicode-xid", 747 | ] 748 | 749 | [[package]] 750 | name = "termcolor" 751 | version = "1.1.2" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 754 | dependencies = [ 755 | "winapi-util", 756 | ] 757 | 758 | [[package]] 759 | name = "tinyvec" 760 | version = "1.5.0" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" 763 | dependencies = [ 764 | "tinyvec_macros", 765 | ] 766 | 767 | [[package]] 768 | name = "tinyvec_macros" 769 | version = "0.1.0" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 772 | 773 | [[package]] 774 | name = "tls-parser" 775 | version = "0.11.0" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "409206e2de64edbf7ea99a44ac31680daf9ef1a57895fb3c5bd738a903691be0" 778 | dependencies = [ 779 | "enum_primitive", 780 | "nom", 781 | "nom-derive", 782 | "phf", 783 | "phf_codegen", 784 | "rusticata-macros", 785 | ] 786 | 787 | [[package]] 788 | name = "tokio" 789 | version = "1.12.0" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" 792 | dependencies = [ 793 | "autocfg", 794 | "bytes 1.1.0", 795 | "libc", 796 | "memchr", 797 | "mio", 798 | "num_cpus", 799 | "once_cell", 800 | "parking_lot", 801 | "pin-project-lite", 802 | "signal-hook-registry", 803 | "tokio-macros", 804 | "winapi", 805 | ] 806 | 807 | [[package]] 808 | name = "tokio-macros" 809 | version = "1.5.0" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" 812 | dependencies = [ 813 | "proc-macro2", 814 | "quote", 815 | "syn", 816 | ] 817 | 818 | [[package]] 819 | name = "unicode-bidi" 820 | version = "0.3.7" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 823 | 824 | [[package]] 825 | name = "unicode-normalization" 826 | version = "0.1.19" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 829 | dependencies = [ 830 | "tinyvec", 831 | ] 832 | 833 | [[package]] 834 | name = "unicode-xid" 835 | version = "0.2.2" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 838 | 839 | [[package]] 840 | name = "url" 841 | version = "2.2.2" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 844 | dependencies = [ 845 | "form_urlencoded", 846 | "idna", 847 | "matches", 848 | "percent-encoding", 849 | ] 850 | 851 | [[package]] 852 | name = "version_check" 853 | version = "0.9.3" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 856 | 857 | [[package]] 858 | name = "wasi" 859 | version = "0.10.2+wasi-snapshot-preview1" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 862 | 863 | [[package]] 864 | name = "winapi" 865 | version = "0.3.9" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 868 | dependencies = [ 869 | "winapi-i686-pc-windows-gnu", 870 | "winapi-x86_64-pc-windows-gnu", 871 | ] 872 | 873 | [[package]] 874 | name = "winapi-i686-pc-windows-gnu" 875 | version = "0.4.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 878 | 879 | [[package]] 880 | name = "winapi-util" 881 | version = "0.1.5" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 884 | dependencies = [ 885 | "winapi", 886 | ] 887 | 888 | [[package]] 889 | name = "winapi-x86_64-pc-windows-gnu" 890 | version = "0.4.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 893 | 894 | [[package]] 895 | name = "yaml-rust" 896 | version = "0.4.5" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 899 | dependencies = [ 900 | "linked-hash-map", 901 | ] 902 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fourth" 3 | version = "0.1.5" 4 | edition = "2021" 5 | authors = ["LI Rui "] 6 | license = "Apache-2.0" 7 | description = "Simple and fast layer 4 proxy in Rust" 8 | readme = "README.md" 9 | homepage = "https://github.com/KernelErr/fourth" 10 | repository = "https://github.com/KernelErr/fourth" 11 | keywords = ["proxy", "network"] 12 | categories = ["web-programming"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | exclude = [".*"] 17 | 18 | [dependencies] 19 | log = "0.4" 20 | pretty_env_logger = "0.4" 21 | serde = { version = "1.0", features = ["derive"] } 22 | serde_yaml = "0.8" 23 | futures = "0.3" 24 | tls-parser = "0.11" 25 | url = "2.2.2" 26 | 27 | tokio = { version = "1.0", features = ["full"] } 28 | 29 | bytes = "1.1" 30 | kcp = "0.4" 31 | byte_string = "1" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | # Fourth 2 | 3 | > Hey, now we are on level 4! 4 | 5 | [![](https://img.shields.io/crates/v/fourth)](https://crates.io/crates/fourth) [![CI](https://img.shields.io/github/workflow/status/kernelerr/fourth/Rust)](https://github.com/KernelErr/fourth/actions/workflows/rust.yml) 6 | 7 | **Under heavy development, version 0.1 may update frequently** 8 | 9 | Fourth is a layer 4 proxy implemented by Rust to listen on specific ports and transfer TCP/KCP data to remote addresses(only TCP) according to configuration. 10 | 11 | ## Features 12 | 13 | - Listen on specific port and proxy to local or remote port 14 | - SNI-based rule without terminating TLS connection 15 | - Allow KCP inbound(warning: untested) 16 | 17 | ## Installation 18 | 19 | To gain best performance on your computer's architecture, please consider build the source code. First, you may need [Rust tool chain](https://rustup.rs/). 20 | 21 | ```bash 22 | $ cd fourth 23 | $ cargo build --release 24 | ``` 25 | 26 | Binary file will be generated at `target/release/fourth`, or you can use `cargo install --path .` to install. 27 | 28 | Or you can use Cargo to install Fourth: 29 | 30 | ```bash 31 | $ cargo install fourth 32 | ``` 33 | 34 | Or you can download binary file form the Release page. 35 | 36 | ## Configuration 37 | 38 | Fourth will read yaml format configuration file from `/etc/fourth/config.yaml`, and you can set custom path to environment variable `FOURTH_CONFIG`, here is an minimal viable example: 39 | 40 | ```yaml 41 | version: 1 42 | log: info 43 | 44 | servers: 45 | proxy_server: 46 | listen: 47 | - "127.0.0.1:8081" 48 | default: remote 49 | 50 | upstream: 51 | remote: "tcp://www.remote.example.com:8082" # proxy to remote address 52 | ``` 53 | 54 | Built-in two upstreams: ban(terminate connection immediately), echo. For detailed configuration, check [this example](./example-config.yaml). 55 | 56 | ## Performance Benchmark 57 | 58 | Tested on 4C2G server: 59 | 60 | Use fourth to proxy to Nginx(QPS of direct connection: ~120000): ~70000 req/s (Command: `wrk -t200 -c1000 -d120s --latency http://proxy-server:8081`) 61 | 62 | Use fourth to proxy to local iperf3: 8Gbps 63 | 64 | ## Thanks 65 | 66 | - [tokio_kcp](https://github.com/Matrix-Zhang/tokio_kcp) 67 | 68 | ## License 69 | 70 | Fourth is available under terms of Apache-2.0. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fourth 2 | 3 | > 这一波在第四层。 4 | 5 | [![](https://img.shields.io/crates/v/fourth)](https://crates.io/crates/fourth) [![CI](https://img.shields.io/github/workflow/status/kernelerr/fourth/Rust)](https://github.com/KernelErr/fourth/actions/workflows/rust.yml) 6 | 7 | [English](/README-EN.md) 8 | 9 | **积极开发中,0.1版本迭代可能较快** 10 | 11 | Fourth是一个Rust实现的Layer 4代理,用于监听指定端口TCP/KCP流量,并根据规则转发到指定目标(目前只支持TCP)。 12 | 13 | ## 功能 14 | 15 | - 监听指定端口代理到本地或远端指定端口 16 | - 监听指定端口,通过TLS ClientHello消息中的SNI进行分流 17 | - 支持KCP入站(警告:未测试) 18 | 19 | ## 安装方法 20 | 21 | 为了确保获得您架构下的最佳性能,请考虑自行编译,首选需要确保您拥有[Rust工具链](https://rustup.rs/)。 22 | 23 | ```bash 24 | $ cd fourth 25 | $ cargo build --release 26 | ``` 27 | 28 | 将在`target/release/fourth`生成二进制文件,您也可以使用`cargo install --path . `来安装二进制文件。 29 | 30 | 或者您也可以使用Cargo直接安装: 31 | 32 | ```bash 33 | $ cargo install fourth 34 | ``` 35 | 36 | 或者您也可以直接从Release中下载二进制文件。 37 | 38 | ## 配置 39 | 40 | Fourth使用yaml格式的配置文件,默认情况下会读取`/etc/fourth/config.yaml`,您也可以设置自定义路径到环境变量`FOURTH_CONFIG`,如下是一个最小有效配置: 41 | 42 | ```yaml 43 | version: 1 44 | log: info 45 | 46 | servers: 47 | proxy_server: 48 | listen: 49 | - "127.0.0.1:8081" 50 | default: remote 51 | 52 | upstream: 53 | remote: "tcp://www.remote.example.com:8082" # proxy to remote address 54 | ``` 55 | 56 | 内置两个的upstream:ban(立即中断连接)、echo(返回读到的数据)。更详细的配置可以参考[示例配置](./example-config.yaml)。 57 | 58 | 注意:[::]会默认同时绑定IPv4和IPv6。 59 | 60 | ## 性能测试 61 | 62 | 在4C2G的服务器上测试: 63 | 64 | 使用Fourth代理到Nginx(直连QPS 120000): ~70000req/s (测试命令:`wrk -t200 -c1000 -d120s --latency http://proxy-server:8081 `) 65 | 66 | 使用Fourth代理到本地iperf3:8Gbps 67 | 68 | ## io_uring? 69 | 70 | 尽管经过了很多尝试,我们发现目前一些Rust下面的io_uring实现存在问题,我们使用的io_uring库实现尽管在吞吐量上可以做到单线程20Gbps(相比之下Tokio仅有8Gbps),但在QPS上存在性能损失较大的问题。因此在有成熟的io_uring实现之前,我们仍然选择epoll。之后我们会持续关注相关进展。 71 | 72 | 可能以后会为Linux高内核版本的用户提供可选的io_uring加速。 73 | 74 | ## 感谢 75 | 76 | - [tokio_kcp](https://github.com/Matrix-Zhang/tokio_kcp) 77 | 78 | ## 协议 79 | 80 | Fourth以Apache-2.0协议开源。 81 | -------------------------------------------------------------------------------- /example-config.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | log: info 3 | 4 | servers: 5 | example_server: 6 | listen: 7 | - "0.0.0.0:443" 8 | - "[::]:443" 9 | tls: true # Enable TLS features like SNI filtering 10 | sni: 11 | proxy.example.com: proxy 12 | www.example.com: nginx 13 | default: ban 14 | proxy_server: 15 | listen: 16 | - "127.0.0.1:8081" 17 | default: remote 18 | kcp_server: 19 | protocol: kcp # default TCP 20 | listen: 21 | - "127.0.0.1:8082" 22 | default: echo 23 | 24 | upstream: 25 | nginx: "tcp://127.0.0.1:8080" 26 | proxy: "tcp://127.0.0.1:1024" 27 | remote: "tcp://www.remote.example.com:8082" # proxy to remote address -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, warn}; 2 | use serde::Deserialize; 3 | use std::collections::{HashMap, HashSet}; 4 | use std::fs::File; 5 | use std::io::{Error as IOError, Read}; 6 | use url::Url; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct Config { 10 | pub base: ParsedConfig, 11 | } 12 | 13 | #[derive(Debug, Default, Deserialize, Clone)] 14 | pub struct ParsedConfig { 15 | pub version: i32, 16 | pub log: Option, 17 | pub servers: HashMap, 18 | pub upstream: HashMap, 19 | } 20 | 21 | #[derive(Debug, Default, Deserialize, Clone)] 22 | pub struct BaseConfig { 23 | pub version: i32, 24 | pub log: Option, 25 | pub servers: HashMap, 26 | pub upstream: HashMap, 27 | } 28 | 29 | #[derive(Debug, Default, Deserialize, Clone)] 30 | pub struct ServerConfig { 31 | pub listen: Vec, 32 | pub protocol: Option, 33 | pub tls: Option, 34 | pub sni: Option>, 35 | pub default: Option, 36 | } 37 | 38 | #[derive(Debug, Clone, Deserialize)] 39 | pub enum Upstream { 40 | Ban, 41 | Echo, 42 | Custom(CustomUpstream), 43 | } 44 | 45 | #[derive(Debug, Clone, Deserialize)] 46 | pub struct CustomUpstream { 47 | pub name: String, 48 | pub addr: String, 49 | pub protocol: String, 50 | } 51 | 52 | #[derive(Debug)] 53 | pub enum ConfigError { 54 | IO(IOError), 55 | Yaml(serde_yaml::Error), 56 | Custom(String), 57 | } 58 | 59 | impl Config { 60 | pub fn new(path: &str) -> Result { 61 | let base = (load_config(path))?; 62 | 63 | Ok(Config { base }) 64 | } 65 | } 66 | 67 | fn load_config(path: &str) -> Result { 68 | let mut contents = String::new(); 69 | let mut file = (File::open(path))?; 70 | (file.read_to_string(&mut contents))?; 71 | 72 | let base: BaseConfig = serde_yaml::from_str(&contents)?; 73 | 74 | if base.version != 1 { 75 | return Err(ConfigError::Custom( 76 | "Unsupported config version".to_string(), 77 | )); 78 | } 79 | 80 | let log_level = base.log.clone().unwrap_or_else(|| "info".to_string()); 81 | if !log_level.eq("disable") { 82 | std::env::set_var("FOURTH_LOG", log_level.clone()); 83 | pretty_env_logger::init_custom_env("FOURTH_LOG"); 84 | debug!("Set log level to {}", log_level); 85 | } 86 | 87 | debug!("Config version {}", base.version); 88 | 89 | let mut parsed_upstream: HashMap = HashMap::new(); 90 | 91 | for (name, upstream) in base.upstream.iter() { 92 | let upstream_url = match Url::parse(upstream) { 93 | Ok(url) => url, 94 | Err(_) => { 95 | return Err(ConfigError::Custom(format!( 96 | "Invalid upstream url {}", 97 | upstream 98 | ))) 99 | } 100 | }; 101 | 102 | let upstream_host = match upstream_url.host_str() { 103 | Some(host) => host, 104 | None => { 105 | return Err(ConfigError::Custom(format!( 106 | "Invalid upstream url {}", 107 | upstream 108 | ))) 109 | } 110 | }; 111 | 112 | let upsteam_port = match upstream_url.port_or_known_default() { 113 | Some(port) => port, 114 | None => { 115 | return Err(ConfigError::Custom(format!( 116 | "Invalid upstream url {}", 117 | upstream 118 | ))) 119 | } 120 | }; 121 | 122 | if upstream_url.scheme() != "tcp" { 123 | return Err(ConfigError::Custom(format!( 124 | "Invalid upstream scheme {}", 125 | upstream 126 | ))); 127 | } 128 | 129 | parsed_upstream.insert( 130 | name.to_string(), 131 | Upstream::Custom(CustomUpstream { 132 | name: name.to_string(), 133 | addr: format!("{}:{}", upstream_host, upsteam_port), 134 | protocol: upstream_url.scheme().to_string(), 135 | }), 136 | ); 137 | } 138 | 139 | parsed_upstream.insert("ban".to_string(), Upstream::Ban); 140 | 141 | parsed_upstream.insert("echo".to_string(), Upstream::Echo); 142 | 143 | let parsed = ParsedConfig { 144 | version: base.version, 145 | log: base.log, 146 | servers: base.servers, 147 | upstream: parsed_upstream, 148 | }; 149 | 150 | verify_config(parsed) 151 | } 152 | 153 | fn verify_config(config: ParsedConfig) -> Result { 154 | let mut used_upstreams: HashSet = HashSet::new(); 155 | let mut upstream_names: HashSet = HashSet::new(); 156 | let mut listen_addresses: HashSet = HashSet::new(); 157 | 158 | // Check for duplicate upstream names 159 | for (name, _) in config.upstream.iter() { 160 | if upstream_names.contains(name) { 161 | return Err(ConfigError::Custom(format!( 162 | "Duplicate upstream name {}", 163 | name 164 | ))); 165 | } 166 | 167 | upstream_names.insert(name.to_string()); 168 | } 169 | 170 | for (_, server) in config.servers.clone() { 171 | // check for duplicate listen addresses 172 | for listen in server.listen { 173 | if listen_addresses.contains(&listen) { 174 | return Err(ConfigError::Custom(format!( 175 | "Duplicate listen address {}", 176 | listen 177 | ))); 178 | } 179 | 180 | listen_addresses.insert(listen.to_string()); 181 | } 182 | 183 | if server.tls.unwrap_or_default() && server.sni.is_some() { 184 | for (_, val) in server.sni.unwrap() { 185 | used_upstreams.insert(val.to_string()); 186 | } 187 | } 188 | 189 | if server.default.is_some() { 190 | used_upstreams.insert(server.default.unwrap().to_string()); 191 | } 192 | 193 | for key in &used_upstreams { 194 | if !config.upstream.contains_key(key) { 195 | return Err(ConfigError::Custom(format!("Upstream {} not found", key))); 196 | } 197 | } 198 | } 199 | 200 | for key in &upstream_names { 201 | if !used_upstreams.contains(key) && !key.eq("echo") && !key.eq("ban") { 202 | warn!("Upstream {} not used", key); 203 | } 204 | } 205 | 206 | Ok(config) 207 | } 208 | 209 | impl From for ConfigError { 210 | fn from(err: IOError) -> ConfigError { 211 | ConfigError::IO(err) 212 | } 213 | } 214 | 215 | impl From for ConfigError { 216 | fn from(err: serde_yaml::Error) -> ConfigError { 217 | ConfigError::Yaml(err) 218 | } 219 | } 220 | 221 | #[cfg(test)] 222 | mod tests { 223 | use super::*; 224 | 225 | #[test] 226 | fn test_load_config() { 227 | let config = Config::new("tests/config.yaml").unwrap(); 228 | assert_eq!(config.base.version, 1); 229 | assert_eq!(config.base.log.unwrap(), "disable"); 230 | assert_eq!(config.base.servers.len(), 5); 231 | assert_eq!(config.base.upstream.len(), 3 + 2); // Add ban and echo upstreams 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod plugins; 3 | mod servers; 4 | 5 | use crate::config::Config; 6 | use crate::servers::Server; 7 | 8 | use log::{debug, error}; 9 | use std::env; 10 | 11 | fn main() { 12 | let config_path = 13 | env::var("FOURTH_CONFIG").unwrap_or_else(|_| "/etc/fourth/config.yaml".to_string()); 14 | 15 | let config = match Config::new(&config_path) { 16 | Ok(config) => config, 17 | Err(e) => { 18 | println!("Could not load config: {:?}", e); 19 | std::process::exit(1); 20 | } 21 | }; 22 | debug!("{:?}", config); 23 | 24 | let mut server = Server::new(config.base); 25 | debug!("{:?}", server); 26 | 27 | let _ = server.run(); 28 | error!("Server ended with errors"); 29 | } 30 | -------------------------------------------------------------------------------- /src/plugins/kcp/config.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, time::Duration}; 2 | 3 | use kcp::Kcp; 4 | 5 | /// Kcp Delay Config 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct KcpNoDelayConfig { 8 | /// Enable nodelay 9 | pub nodelay: bool, 10 | /// Internal update interval (ms) 11 | pub interval: i32, 12 | /// ACK number to enable fast resend 13 | pub resend: i32, 14 | /// Disable congetion control 15 | pub nc: bool, 16 | } 17 | 18 | impl Default for KcpNoDelayConfig { 19 | fn default() -> KcpNoDelayConfig { 20 | KcpNoDelayConfig { 21 | nodelay: false, 22 | interval: 100, 23 | resend: 0, 24 | nc: false, 25 | } 26 | } 27 | } 28 | 29 | #[allow(unused)] 30 | impl KcpNoDelayConfig { 31 | /// Get a fastest configuration 32 | /// 33 | /// 1. Enable NoDelay 34 | /// 2. Set ticking interval to be 10ms 35 | /// 3. Set fast resend to be 2 36 | /// 4. Disable congestion control 37 | pub fn fastest() -> KcpNoDelayConfig { 38 | KcpNoDelayConfig { 39 | nodelay: true, 40 | interval: 10, 41 | resend: 2, 42 | nc: true, 43 | } 44 | } 45 | 46 | /// Get a normal configuration 47 | /// 48 | /// 1. Disable NoDelay 49 | /// 2. Set ticking interval to be 40ms 50 | /// 3. Disable fast resend 51 | /// 4. Enable congestion control 52 | pub fn normal() -> KcpNoDelayConfig { 53 | KcpNoDelayConfig { 54 | nodelay: false, 55 | interval: 40, 56 | resend: 0, 57 | nc: false, 58 | } 59 | } 60 | } 61 | 62 | /// Kcp Config 63 | #[derive(Debug, Clone, Copy)] 64 | pub struct KcpConfig { 65 | /// Max Transmission Unit 66 | pub mtu: usize, 67 | /// nodelay 68 | pub nodelay: KcpNoDelayConfig, 69 | /// Send window size 70 | pub wnd_size: (u16, u16), 71 | /// Session expire duration, default is 90 seconds 72 | pub session_expire: Duration, 73 | /// Flush KCP state immediately after write 74 | pub flush_write: bool, 75 | /// Flush ACKs immediately after input 76 | pub flush_acks_input: bool, 77 | /// Stream mode 78 | pub stream: bool, 79 | } 80 | 81 | impl Default for KcpConfig { 82 | fn default() -> KcpConfig { 83 | KcpConfig { 84 | mtu: 1400, 85 | nodelay: KcpNoDelayConfig::normal(), 86 | wnd_size: (256, 256), 87 | session_expire: Duration::from_secs(90), 88 | flush_write: false, 89 | flush_acks_input: false, 90 | stream: true, 91 | } 92 | } 93 | } 94 | 95 | impl KcpConfig { 96 | /// Applies config onto `Kcp` 97 | #[doc(hidden)] 98 | pub fn apply_config(&self, k: &mut Kcp) { 99 | k.set_mtu(self.mtu).expect("invalid MTU"); 100 | 101 | k.set_nodelay( 102 | self.nodelay.nodelay, 103 | self.nodelay.interval, 104 | self.nodelay.resend, 105 | self.nodelay.nc, 106 | ); 107 | 108 | k.set_wndsize(self.wnd_size.0, self.wnd_size.1); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/plugins/kcp/listener.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, ErrorKind}, 3 | net::SocketAddr, 4 | sync::Arc, 5 | time::Duration, 6 | }; 7 | 8 | use byte_string::ByteStr; 9 | use kcp::{Error as KcpError, KcpResult}; 10 | use log::{debug, error, trace}; 11 | use tokio::{ 12 | net::{ToSocketAddrs, UdpSocket}, 13 | sync::mpsc, 14 | task::JoinHandle, 15 | time, 16 | }; 17 | 18 | use crate::plugins::kcp::{config::KcpConfig, session::KcpSessionManager, stream::KcpStream}; 19 | 20 | #[allow(unused)] 21 | pub struct KcpListener { 22 | udp: Arc, 23 | accept_rx: mpsc::Receiver<(KcpStream, SocketAddr)>, 24 | task_watcher: JoinHandle<()>, 25 | } 26 | 27 | impl Drop for KcpListener { 28 | fn drop(&mut self) { 29 | self.task_watcher.abort(); 30 | } 31 | } 32 | 33 | impl KcpListener { 34 | pub async fn bind(config: KcpConfig, addr: A) -> KcpResult { 35 | let udp = UdpSocket::bind(addr).await?; 36 | let udp = Arc::new(udp); 37 | let server_udp = udp.clone(); 38 | 39 | let (accept_tx, accept_rx) = mpsc::channel(1024 /* backlogs */); 40 | let task_watcher = tokio::spawn(async move { 41 | let (close_tx, mut close_rx) = mpsc::channel(64); 42 | 43 | let mut sessions = KcpSessionManager::new(); 44 | let mut packet_buffer = [0u8; 65536]; 45 | loop { 46 | tokio::select! { 47 | conv = close_rx.recv() => { 48 | let conv = conv.expect("close_tx closed unexpectly"); 49 | sessions.close_conv(conv); 50 | trace!("session conv: {} removed", conv); 51 | } 52 | 53 | recv_res = udp.recv_from(&mut packet_buffer) => { 54 | match recv_res { 55 | Err(err) => { 56 | error!("udp.recv_from failed, error: {}", err); 57 | time::sleep(Duration::from_secs(1)).await; 58 | } 59 | Ok((n, peer_addr)) => { 60 | let packet = &mut packet_buffer[..n]; 61 | 62 | log::trace!("received peer: {}, {:?}", peer_addr, ByteStr::new(packet)); 63 | 64 | let mut conv = kcp::get_conv(packet); 65 | if conv == 0 { 66 | // Allocate a conv for client. 67 | conv = sessions.alloc_conv(); 68 | debug!("allocate {} conv for peer: {}", conv, peer_addr); 69 | 70 | kcp::set_conv(packet, conv); 71 | } 72 | 73 | let session = match sessions.get_or_create(&config, conv, &udp, peer_addr, &close_tx) { 74 | Ok((s, created)) => { 75 | if created { 76 | // Created a new session, constructed a new accepted client 77 | let stream = KcpStream::with_session(s.clone()); 78 | if let Err(..) = accept_tx.try_send((stream, peer_addr)) { 79 | debug!("failed to create accepted stream due to channel failure"); 80 | 81 | // remove it from session 82 | sessions.close_conv(conv); 83 | continue; 84 | } 85 | } 86 | 87 | s 88 | }, 89 | Err(err) => { 90 | error!("failed to create session, error: {}, peer: {}, conv: {}", err, peer_addr, conv); 91 | continue; 92 | } 93 | }; 94 | 95 | // let mut kcp = session.kcp_socket().lock().await; 96 | // if let Err(err) = kcp.input(packet) { 97 | // error!("kcp.input failed, peer: {}, conv: {}, error: {}, packet: {:?}", peer_addr, conv, err, ByteStr::new(packet)); 98 | // } 99 | session.input(packet).await; 100 | } 101 | } 102 | } 103 | } 104 | } 105 | }); 106 | 107 | Ok(KcpListener { 108 | udp: server_udp, 109 | accept_rx, 110 | task_watcher, 111 | }) 112 | } 113 | 114 | pub async fn accept(&mut self) -> KcpResult<(KcpStream, SocketAddr)> { 115 | match self.accept_rx.recv().await { 116 | Some(s) => Ok(s), 117 | None => Err(KcpError::IoError(io::Error::new( 118 | ErrorKind::Other, 119 | "accept channel closed unexpectly", 120 | ))), 121 | } 122 | } 123 | 124 | #[allow(unused)] 125 | pub fn local_addr(&self) -> io::Result { 126 | self.udp.local_addr() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/plugins/kcp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Library of KCP on Tokio 2 | 3 | pub use self::{ 4 | config::{KcpConfig, KcpNoDelayConfig}, 5 | listener::KcpListener, 6 | stream::KcpStream, 7 | }; 8 | 9 | mod config; 10 | mod listener; 11 | mod session; 12 | mod skcp; 13 | mod stream; 14 | mod utils; 15 | -------------------------------------------------------------------------------- /src/plugins/kcp/session.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{hash_map::Entry, HashMap}, 3 | net::SocketAddr, 4 | sync::{ 5 | atomic::{AtomicBool, Ordering}, 6 | Arc, 7 | }, 8 | time::Duration, 9 | }; 10 | 11 | use byte_string::ByteStr; 12 | use kcp::KcpResult; 13 | use log::{error, trace}; 14 | use tokio::{ 15 | net::UdpSocket, 16 | sync::{mpsc, Mutex}, 17 | time::{self, Instant}, 18 | }; 19 | 20 | use crate::plugins::kcp::{skcp::KcpSocket, KcpConfig}; 21 | 22 | pub struct KcpSession { 23 | socket: Mutex, 24 | closed: AtomicBool, 25 | session_expire: Duration, 26 | session_close_notifier: Option>, 27 | input_tx: mpsc::Sender>, 28 | } 29 | 30 | impl KcpSession { 31 | fn new( 32 | socket: KcpSocket, 33 | session_expire: Duration, 34 | session_close_notifier: Option>, 35 | input_tx: mpsc::Sender>, 36 | ) -> KcpSession { 37 | KcpSession { 38 | socket: Mutex::new(socket), 39 | closed: AtomicBool::new(false), 40 | session_expire, 41 | session_close_notifier, 42 | input_tx, 43 | } 44 | } 45 | 46 | pub fn new_shared( 47 | socket: KcpSocket, 48 | session_expire: Duration, 49 | session_close_notifier: Option>, 50 | ) -> Arc { 51 | let is_client = session_close_notifier.is_none(); 52 | 53 | let (input_tx, mut input_rx) = mpsc::channel(64); 54 | 55 | let udp_socket = socket.udp_socket().clone(); 56 | 57 | let session = Arc::new(KcpSession::new( 58 | socket, 59 | session_expire, 60 | session_close_notifier, 61 | input_tx, 62 | )); 63 | 64 | { 65 | let session = session.clone(); 66 | tokio::spawn(async move { 67 | let mut input_buffer = [0u8; 65536]; 68 | let update_timer = time::sleep(Duration::from_millis(10)); 69 | tokio::pin!(update_timer); 70 | 71 | loop { 72 | tokio::select! { 73 | // recv() then input() 74 | // Drives the KCP machine forward 75 | recv_result = udp_socket.recv(&mut input_buffer), if is_client => { 76 | match recv_result { 77 | Err(err) => { 78 | error!("[SESSION] UDP recv failed, error: {}", err); 79 | } 80 | Ok(n) => { 81 | let input_buffer = &input_buffer[..n]; 82 | trace!("[SESSION] UDP recv {} bytes, going to input {:?}", n, ByteStr::new(input_buffer)); 83 | 84 | let mut socket = session.socket.lock().await; 85 | 86 | match socket.input(input_buffer) { 87 | Ok(true) => { 88 | trace!("[SESSION] UDP input {} bytes and waked sender/receiver", n); 89 | } 90 | Ok(false) => {} 91 | Err(err) => { 92 | error!("[SESSION] UDP input {} bytes error: {}, input buffer {:?}", n, err, ByteStr::new(input_buffer)); 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | // bytes received from listener socket 100 | input_opt = input_rx.recv() => { 101 | if let Some(input_buffer) = input_opt { 102 | let mut socket = session.socket.lock().await; 103 | match socket.input(&input_buffer) { 104 | Ok(..) => { 105 | trace!("[SESSION] UDP input {} bytes from channel {:?}", input_buffer.len(), ByteStr::new(&input_buffer)); 106 | } 107 | Err(err) => { 108 | error!("[SESSION] UDP input {} bytes from channel failed, error: {}, input buffer {:?}", 109 | input_buffer.len(), err, ByteStr::new(&input_buffer)); 110 | } 111 | } 112 | } 113 | } 114 | 115 | // Call update() in period 116 | _ = &mut update_timer => { 117 | let mut socket = session.socket.lock().await; 118 | 119 | let is_closed = session.closed.load(Ordering::Acquire); 120 | if is_closed && socket.can_close() { 121 | trace!("[SESSION] KCP session closed"); 122 | break; 123 | } 124 | 125 | // server socket expires 126 | if !is_client { 127 | // If this is a server stream, close it automatically after a period of time 128 | let last_update_time = socket.last_update_time(); 129 | let elapsed = last_update_time.elapsed(); 130 | 131 | if elapsed > session.session_expire { 132 | if elapsed > session.session_expire * 2 { 133 | // Force close. Client may have already gone. 134 | trace!( 135 | "[SESSION] force close inactive session, conv: {}, last_update: {}s ago", 136 | socket.conv(), 137 | elapsed.as_secs() 138 | ); 139 | break; 140 | } 141 | 142 | if !is_closed { 143 | trace!( 144 | "[SESSION] closing inactive session, conv: {}, last_update: {}s ago", 145 | socket.conv(), 146 | elapsed.as_secs() 147 | ); 148 | session.closed.store(true, Ordering::Release); 149 | } 150 | } 151 | } 152 | 153 | match socket.update() { 154 | Ok(next_next) => { 155 | update_timer.as_mut().reset(Instant::from_std(next_next)); 156 | } 157 | Err(err) => { 158 | error!("[SESSION] KCP update failed, error: {}", err); 159 | update_timer.as_mut().reset(Instant::now() + Duration::from_millis(10)); 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | { 167 | // Close the socket. 168 | // Wake all pending tasks and let all send/recv return EOF 169 | 170 | let mut socket = session.socket.lock().await; 171 | socket.close(); 172 | } 173 | 174 | if let Some(ref notifier) = session.session_close_notifier { 175 | let socket = session.socket.lock().await; 176 | let _ = notifier.send(socket.conv()).await; 177 | } 178 | }); 179 | } 180 | 181 | session 182 | } 183 | 184 | pub fn kcp_socket(&self) -> &Mutex { 185 | &self.socket 186 | } 187 | 188 | pub fn close(&self) { 189 | self.closed.store(true, Ordering::Release); 190 | } 191 | 192 | pub async fn input(&self, buf: &[u8]) { 193 | self.input_tx 194 | .send(buf.to_owned()) 195 | .await 196 | .expect("input channel closed") 197 | } 198 | } 199 | 200 | pub struct KcpSessionManager { 201 | sessions: HashMap>, 202 | next_free_conv: u32, 203 | } 204 | 205 | impl KcpSessionManager { 206 | pub fn new() -> KcpSessionManager { 207 | KcpSessionManager { 208 | sessions: HashMap::new(), 209 | next_free_conv: 0, 210 | } 211 | } 212 | 213 | pub fn close_conv(&mut self, conv: u32) { 214 | self.sessions.remove(&conv); 215 | } 216 | 217 | pub fn alloc_conv(&mut self) -> u32 { 218 | loop { 219 | let (mut c, _) = self.next_free_conv.overflowing_add(1); 220 | if c == 0 { 221 | let (nc, _) = c.overflowing_add(1); 222 | c = nc; 223 | } 224 | self.next_free_conv = c; 225 | 226 | if self.sessions.get(&self.next_free_conv).is_none() { 227 | let conv = self.next_free_conv; 228 | return conv; 229 | } 230 | } 231 | } 232 | 233 | pub fn get_or_create( 234 | &mut self, 235 | config: &KcpConfig, 236 | conv: u32, 237 | udp: &Arc, 238 | peer_addr: SocketAddr, 239 | session_close_notifier: &mpsc::Sender, 240 | ) -> KcpResult<(Arc, bool)> { 241 | match self.sessions.entry(conv) { 242 | Entry::Occupied(occ) => Ok((occ.get().clone(), false)), 243 | Entry::Vacant(vac) => { 244 | let socket = KcpSocket::new(config, conv, udp.clone(), peer_addr, config.stream)?; 245 | let session = KcpSession::new_shared( 246 | socket, 247 | config.session_expire, 248 | Some(session_close_notifier.clone()), 249 | ); 250 | trace!("created session for conv: {}, peer: {}", conv, peer_addr); 251 | vac.insert(session.clone()); 252 | Ok((session, true)) 253 | } 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/plugins/kcp/skcp.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, ErrorKind, Write}, 3 | net::SocketAddr, 4 | sync::Arc, 5 | task::{Context, Poll, Waker}, 6 | time::{Duration, Instant}, 7 | }; 8 | 9 | use futures::future; 10 | use kcp::{Error as KcpError, Kcp, KcpResult}; 11 | use log::{error, trace}; 12 | use tokio::{net::UdpSocket, sync::mpsc}; 13 | 14 | use crate::plugins::kcp::{utils::now_millis, KcpConfig}; 15 | 16 | /// Writer for sending packets to the underlying UdpSocket 17 | struct UdpOutput { 18 | socket: Arc, 19 | target_addr: SocketAddr, 20 | delay_tx: mpsc::UnboundedSender>, 21 | } 22 | 23 | impl UdpOutput { 24 | /// Create a new Writer for writing packets to UdpSocket 25 | pub fn new(socket: Arc, target_addr: SocketAddr) -> UdpOutput { 26 | let (delay_tx, mut delay_rx) = mpsc::unbounded_channel::>(); 27 | 28 | { 29 | let socket = socket.clone(); 30 | tokio::spawn(async move { 31 | while let Some(buf) = delay_rx.recv().await { 32 | if let Err(err) = socket.send_to(&buf, target_addr).await { 33 | error!("[SEND] UDP delayed send failed, error: {}", err); 34 | } 35 | } 36 | }); 37 | } 38 | 39 | UdpOutput { 40 | socket, 41 | target_addr, 42 | delay_tx, 43 | } 44 | } 45 | } 46 | 47 | impl Write for UdpOutput { 48 | fn write(&mut self, buf: &[u8]) -> io::Result { 49 | match self.socket.try_send_to(buf, self.target_addr) { 50 | Ok(n) => Ok(n), 51 | Err(ref err) if err.kind() == ErrorKind::WouldBlock => { 52 | // send return EAGAIN 53 | // ignored as packet was lost in transmission 54 | trace!( 55 | "[SEND] UDP send EAGAIN, packet.size: {} bytes, delayed send", 56 | buf.len() 57 | ); 58 | 59 | self.delay_tx 60 | .send(buf.to_owned()) 61 | .expect("channel closed unexpectly"); 62 | 63 | Ok(buf.len()) 64 | } 65 | Err(err) => Err(err), 66 | } 67 | } 68 | 69 | fn flush(&mut self) -> io::Result<()> { 70 | Ok(()) 71 | } 72 | } 73 | 74 | pub struct KcpSocket { 75 | kcp: Kcp, 76 | last_update: Instant, 77 | socket: Arc, 78 | flush_write: bool, 79 | flush_ack_input: bool, 80 | sent_first: bool, 81 | pending_sender: Option, 82 | pending_receiver: Option, 83 | closed: bool, 84 | } 85 | 86 | impl KcpSocket { 87 | pub fn new( 88 | c: &KcpConfig, 89 | conv: u32, 90 | socket: Arc, 91 | target_addr: SocketAddr, 92 | stream: bool, 93 | ) -> KcpResult { 94 | let output = UdpOutput::new(socket.clone(), target_addr); 95 | let mut kcp = if stream { 96 | Kcp::new_stream(conv, output) 97 | } else { 98 | Kcp::new(conv, output) 99 | }; 100 | c.apply_config(&mut kcp); 101 | 102 | // Ask server to allocate one 103 | if conv == 0 { 104 | kcp.input_conv(); 105 | } 106 | 107 | kcp.update(now_millis())?; 108 | 109 | Ok(KcpSocket { 110 | kcp, 111 | last_update: Instant::now(), 112 | socket, 113 | flush_write: c.flush_write, 114 | flush_ack_input: c.flush_acks_input, 115 | sent_first: false, 116 | pending_sender: None, 117 | pending_receiver: None, 118 | closed: false, 119 | }) 120 | } 121 | 122 | /// Call every time you got data from transmission 123 | pub fn input(&mut self, buf: &[u8]) -> KcpResult { 124 | match self.kcp.input(buf) { 125 | Ok(..) => {} 126 | Err(KcpError::ConvInconsistent(expected, actual)) => { 127 | trace!( 128 | "[INPUT] Conv expected={} actual={} ignored", 129 | expected, 130 | actual 131 | ); 132 | return Ok(false); 133 | } 134 | Err(err) => return Err(err), 135 | } 136 | self.last_update = Instant::now(); 137 | 138 | if self.flush_ack_input { 139 | self.kcp.flush_ack()?; 140 | } 141 | 142 | Ok(self.try_wake_pending_waker()) 143 | } 144 | 145 | /// Call if you want to send some data 146 | pub fn poll_send(&mut self, cx: &mut Context<'_>, mut buf: &[u8]) -> Poll> { 147 | if self.closed { 148 | return Ok(0).into(); 149 | } 150 | 151 | // If: 152 | // 1. Have sent the first packet (asking for conv) 153 | // 2. Too many pending packets 154 | if self.sent_first 155 | && (self.kcp.wait_snd() >= self.kcp.snd_wnd() as usize || self.kcp.waiting_conv()) 156 | { 157 | trace!( 158 | "[SEND] waitsnd={} sndwnd={} excceeded or waiting conv={}", 159 | self.kcp.wait_snd(), 160 | self.kcp.snd_wnd(), 161 | self.kcp.waiting_conv() 162 | ); 163 | self.pending_sender = Some(cx.waker().clone()); 164 | return Poll::Pending; 165 | } 166 | 167 | if !self.sent_first && self.kcp.waiting_conv() && buf.len() > self.kcp.mss() as usize { 168 | buf = &buf[..self.kcp.mss() as usize]; 169 | } 170 | 171 | let n = self.kcp.send(buf)?; 172 | self.sent_first = true; 173 | self.last_update = Instant::now(); 174 | 175 | if self.flush_write { 176 | self.kcp.flush()?; 177 | } 178 | 179 | Ok(n).into() 180 | } 181 | 182 | /// Call if you want to send some data 183 | #[allow(dead_code)] 184 | pub async fn send(&mut self, buf: &[u8]) -> KcpResult { 185 | future::poll_fn(|cx| self.poll_send(cx, buf)).await 186 | } 187 | 188 | #[allow(dead_code)] 189 | pub fn try_recv(&mut self, buf: &mut [u8]) -> KcpResult { 190 | if self.closed { 191 | return Ok(0); 192 | } 193 | self.kcp.recv(buf) 194 | } 195 | 196 | pub fn poll_recv(&mut self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { 197 | if self.closed { 198 | return Ok(0).into(); 199 | } 200 | 201 | match self.kcp.recv(buf) { 202 | Ok(n) => Ok(n).into(), 203 | Err(KcpError::RecvQueueEmpty) => { 204 | self.pending_receiver = Some(cx.waker().clone()); 205 | Poll::Pending 206 | } 207 | Err(err) => Err(err).into(), 208 | } 209 | } 210 | 211 | #[allow(dead_code)] 212 | pub async fn recv(&mut self, buf: &mut [u8]) -> KcpResult { 213 | future::poll_fn(|cx| self.poll_recv(cx, buf)).await 214 | } 215 | 216 | pub fn flush(&mut self) -> KcpResult<()> { 217 | self.kcp.flush()?; 218 | self.last_update = Instant::now(); 219 | Ok(()) 220 | } 221 | 222 | fn try_wake_pending_waker(&mut self) -> bool { 223 | let mut waked = false; 224 | 225 | if self.pending_sender.is_some() 226 | && self.kcp.wait_snd() < self.kcp.snd_wnd() as usize 227 | && !self.kcp.waiting_conv() 228 | { 229 | let waker = self.pending_sender.take().unwrap(); 230 | waker.wake(); 231 | 232 | waked = true; 233 | } 234 | 235 | if self.pending_receiver.is_some() { 236 | if let Ok(peek) = self.kcp.peeksize() { 237 | if peek > 0 { 238 | let waker = self.pending_receiver.take().unwrap(); 239 | waker.wake(); 240 | 241 | waked = true; 242 | } 243 | } 244 | } 245 | 246 | waked 247 | } 248 | 249 | pub fn update(&mut self) -> KcpResult { 250 | let now = now_millis(); 251 | self.kcp.update(now)?; 252 | let next = self.kcp.check(now); 253 | 254 | self.try_wake_pending_waker(); 255 | 256 | Ok(Instant::now() + Duration::from_millis(next as u64)) 257 | } 258 | 259 | pub fn close(&mut self) { 260 | self.closed = true; 261 | if let Some(w) = self.pending_sender.take() { 262 | w.wake(); 263 | } 264 | if let Some(w) = self.pending_receiver.take() { 265 | w.wake(); 266 | } 267 | } 268 | 269 | pub fn udp_socket(&self) -> &Arc { 270 | &self.socket 271 | } 272 | 273 | pub fn can_close(&self) -> bool { 274 | self.kcp.wait_snd() == 0 275 | } 276 | 277 | pub fn conv(&self) -> u32 { 278 | self.kcp.conv() 279 | } 280 | 281 | pub fn peek_size(&self) -> KcpResult { 282 | self.kcp.peeksize() 283 | } 284 | 285 | pub fn last_update_time(&self) -> Instant { 286 | self.last_update 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/plugins/kcp/stream.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, ErrorKind}, 3 | net::{IpAddr, SocketAddr}, 4 | pin::Pin, 5 | sync::Arc, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | use futures::{future, ready}; 10 | use kcp::{Error as KcpError, KcpResult}; 11 | use log::trace; 12 | use tokio::{ 13 | io::{AsyncRead, AsyncWrite, ReadBuf}, 14 | net::UdpSocket, 15 | }; 16 | 17 | use crate::plugins::kcp::{config::KcpConfig, session::KcpSession, skcp::KcpSocket}; 18 | 19 | pub struct KcpStream { 20 | session: Arc, 21 | recv_buffer: Vec, 22 | recv_buffer_pos: usize, 23 | recv_buffer_cap: usize, 24 | } 25 | 26 | impl Drop for KcpStream { 27 | fn drop(&mut self) { 28 | self.session.close(); 29 | } 30 | } 31 | 32 | #[allow(unused)] 33 | impl KcpStream { 34 | pub async fn connect(config: &KcpConfig, addr: SocketAddr) -> KcpResult { 35 | let udp = match addr.ip() { 36 | IpAddr::V4(..) => UdpSocket::bind("0.0.0.0:0").await?, 37 | IpAddr::V6(..) => UdpSocket::bind("[::]:0").await?, 38 | }; 39 | 40 | let udp = Arc::new(udp); 41 | let socket = KcpSocket::new(config, 0, udp, addr, config.stream)?; 42 | 43 | let session = KcpSession::new_shared(socket, config.session_expire, None); 44 | 45 | Ok(KcpStream::with_session(session)) 46 | } 47 | 48 | pub(crate) fn with_session(session: Arc) -> KcpStream { 49 | KcpStream { 50 | session, 51 | recv_buffer: Vec::new(), 52 | recv_buffer_pos: 0, 53 | recv_buffer_cap: 0, 54 | } 55 | } 56 | 57 | pub fn poll_send(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { 58 | // Mutex doesn't have poll_lock, spinning on it. 59 | let socket = self.session.kcp_socket(); 60 | let mut kcp = match socket.try_lock() { 61 | Ok(guard) => guard, 62 | Err(..) => { 63 | cx.waker().wake_by_ref(); 64 | return Poll::Pending; 65 | } 66 | }; 67 | 68 | kcp.poll_send(cx, buf) 69 | } 70 | 71 | pub async fn send(&mut self, buf: &[u8]) -> KcpResult { 72 | future::poll_fn(|cx| self.poll_send(cx, buf)).await 73 | } 74 | 75 | pub fn poll_recv(&mut self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { 76 | loop { 77 | // Consumes all data in buffer 78 | if self.recv_buffer_pos < self.recv_buffer_cap { 79 | let remaining = self.recv_buffer_cap - self.recv_buffer_pos; 80 | let copy_length = remaining.min(buf.len()); 81 | 82 | buf.copy_from_slice( 83 | &self.recv_buffer[self.recv_buffer_pos..self.recv_buffer_pos + copy_length], 84 | ); 85 | self.recv_buffer_pos += copy_length; 86 | return Ok(copy_length).into(); 87 | } 88 | 89 | // Mutex doesn't have poll_lock, spinning on it. 90 | let socket = self.session.kcp_socket(); 91 | let mut kcp = match socket.try_lock() { 92 | Ok(guard) => guard, 93 | Err(..) => { 94 | cx.waker().wake_by_ref(); 95 | return Poll::Pending; 96 | } 97 | }; 98 | 99 | // Try to read from KCP 100 | // 1. Read directly with user provided `buf` 101 | match ready!(kcp.poll_recv(cx, buf)) { 102 | Ok(n) => { 103 | trace!("[CLIENT] recv directly {} bytes", n); 104 | return Ok(n).into(); 105 | } 106 | Err(KcpError::UserBufTooSmall) => {} 107 | Err(err) => return Err(err).into(), 108 | } 109 | 110 | // 2. User `buf` too small, read to recv_buffer 111 | let required_size = kcp.peek_size()?; 112 | if self.recv_buffer.len() < required_size { 113 | self.recv_buffer.resize(required_size, 0); 114 | } 115 | 116 | match ready!(kcp.poll_recv(cx, &mut self.recv_buffer)) { 117 | Ok(n) => { 118 | trace!("[CLIENT] recv buffered {} bytes", n); 119 | self.recv_buffer_pos = 0; 120 | self.recv_buffer_cap = n; 121 | } 122 | Err(err) => return Err(err).into(), 123 | } 124 | } 125 | } 126 | 127 | pub async fn recv(&mut self, buf: &mut [u8]) -> KcpResult { 128 | future::poll_fn(|cx| self.poll_recv(cx, buf)).await 129 | } 130 | } 131 | 132 | impl AsyncRead for KcpStream { 133 | fn poll_read( 134 | mut self: Pin<&mut Self>, 135 | cx: &mut Context<'_>, 136 | buf: &mut ReadBuf<'_>, 137 | ) -> Poll> { 138 | match ready!(self.poll_recv(cx, buf.initialize_unfilled())) { 139 | Ok(n) => { 140 | buf.advance(n); 141 | Ok(()).into() 142 | } 143 | Err(KcpError::IoError(err)) => Err(err).into(), 144 | Err(err) => Err(io::Error::new(ErrorKind::Other, err)).into(), 145 | } 146 | } 147 | } 148 | 149 | impl AsyncWrite for KcpStream { 150 | fn poll_write( 151 | mut self: Pin<&mut Self>, 152 | cx: &mut Context<'_>, 153 | buf: &[u8], 154 | ) -> Poll> { 155 | match ready!(self.poll_send(cx, buf)) { 156 | Ok(n) => Ok(n).into(), 157 | Err(KcpError::IoError(err)) => Err(err).into(), 158 | Err(err) => Err(io::Error::new(ErrorKind::Other, err)).into(), 159 | } 160 | } 161 | 162 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 163 | // Mutex doesn't have poll_lock, spinning on it. 164 | let socket = self.session.kcp_socket(); 165 | let mut kcp = match socket.try_lock() { 166 | Ok(guard) => guard, 167 | Err(..) => { 168 | cx.waker().wake_by_ref(); 169 | return Poll::Pending; 170 | } 171 | }; 172 | 173 | match kcp.flush() { 174 | Ok(..) => Ok(()).into(), 175 | Err(KcpError::IoError(err)) => Err(err).into(), 176 | Err(err) => Err(io::Error::new(ErrorKind::Other, err)).into(), 177 | } 178 | } 179 | 180 | fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 181 | Ok(()).into() 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/plugins/kcp/utils.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | 3 | #[inline] 4 | pub fn now_millis() -> u32 { 5 | let start = SystemTime::now(); 6 | let since_the_epoch = start 7 | .duration_since(UNIX_EPOCH) 8 | .expect("time went afterwards"); 9 | (since_the_epoch.as_secs() * 1000 + since_the_epoch.subsec_millis() as u64 / 1_000_000) as u32 10 | } 11 | -------------------------------------------------------------------------------- /src/plugins/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod kcp; 2 | -------------------------------------------------------------------------------- /src/servers/mod.rs: -------------------------------------------------------------------------------- 1 | use log::{error, info}; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::net::SocketAddr; 4 | use std::sync::Arc; 5 | use tokio::task::JoinHandle; 6 | 7 | mod protocol; 8 | use crate::config::{ParsedConfig, Upstream}; 9 | use protocol::{kcp, tcp}; 10 | 11 | #[derive(Debug)] 12 | pub struct Server { 13 | pub proxies: Vec>, 14 | pub config: ParsedConfig, 15 | } 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct Proxy { 19 | pub name: String, 20 | pub listen: SocketAddr, 21 | pub protocol: String, 22 | pub tls: bool, 23 | pub sni: Option>, 24 | pub default: String, 25 | pub upstream: HashMap, 26 | } 27 | 28 | impl Server { 29 | pub fn new(config: ParsedConfig) -> Self { 30 | let mut new_server = Server { 31 | proxies: Vec::new(), 32 | config: config.clone(), 33 | }; 34 | 35 | for (name, proxy) in config.servers.iter() { 36 | let protocol = proxy.protocol.clone().unwrap_or_else(|| "tcp".to_string()); 37 | let tls = proxy.tls.unwrap_or(false); 38 | let sni = proxy.sni.clone(); 39 | let default = proxy.default.clone().unwrap_or_else(|| "ban".to_string()); 40 | let upstream = config.upstream.clone(); 41 | let mut upstream_set: HashSet = HashSet::new(); 42 | for key in upstream.keys() { 43 | if key.eq("ban") || key.eq("echo") { 44 | continue; 45 | } 46 | upstream_set.insert(key.clone()); 47 | } 48 | for listen in proxy.listen.clone() { 49 | let listen_addr: SocketAddr = match listen.parse() { 50 | Ok(addr) => addr, 51 | Err(_) => { 52 | error!("Invalid listen address: {}", listen); 53 | continue; 54 | } 55 | }; 56 | 57 | let proxy = Proxy { 58 | name: name.clone(), 59 | listen: listen_addr, 60 | protocol: protocol.clone(), 61 | tls, 62 | sni: sni.clone(), 63 | default: default.clone(), 64 | upstream: upstream.clone(), 65 | }; 66 | new_server.proxies.push(Arc::new(proxy)); 67 | } 68 | } 69 | 70 | new_server 71 | } 72 | 73 | #[tokio::main] 74 | pub async fn run(&mut self) -> Result<(), Box> { 75 | let proxies = self.proxies.clone(); 76 | let mut handles: Vec> = Vec::new(); 77 | 78 | for config in proxies { 79 | info!( 80 | "Starting {} server {} on {}", 81 | config.protocol, config.name, config.listen 82 | ); 83 | let handle = tokio::spawn(async move { 84 | match config.protocol.as_ref() { 85 | "tcp" => { 86 | let res = tcp::proxy(config.clone()).await; 87 | if res.is_err() { 88 | error!("Failed to start {}: {}", config.name, res.err().unwrap()); 89 | } 90 | } 91 | "kcp" => { 92 | let res = kcp::proxy(config.clone()).await; 93 | if res.is_err() { 94 | error!("Failed to start {}: {}", config.name, res.err().unwrap()); 95 | } 96 | } 97 | _ => { 98 | error!("Invalid protocol: {}", config.protocol) 99 | } 100 | } 101 | }); 102 | handles.push(handle); 103 | } 104 | 105 | for handle in handles { 106 | handle.await?; 107 | } 108 | Ok(()) 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use crate::plugins::kcp::{KcpConfig, KcpStream}; 115 | use std::net::SocketAddr; 116 | use std::thread::{self, sleep}; 117 | use std::time::Duration; 118 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 119 | use tokio::net::{TcpListener, TcpStream}; 120 | 121 | use super::*; 122 | 123 | #[tokio::main] 124 | async fn tcp_mock_server() { 125 | let server_addr: SocketAddr = "127.0.0.1:54599".parse().unwrap(); 126 | let listener = TcpListener::bind(server_addr).await.unwrap(); 127 | loop { 128 | let (mut stream, _) = listener.accept().await.unwrap(); 129 | let mut buf = [0u8; 2]; 130 | let mut n = stream.read(&mut buf).await.unwrap(); 131 | while n > 0 { 132 | stream.write(b"hello").await.unwrap(); 133 | if buf.eq(b"by") { 134 | stream.shutdown().await.unwrap(); 135 | break; 136 | } 137 | n = stream.read(&mut buf).await.unwrap(); 138 | } 139 | stream.shutdown().await.unwrap(); 140 | } 141 | } 142 | 143 | #[tokio::test] 144 | async fn test_proxy() { 145 | use crate::config::Config; 146 | let config = Config::new("tests/config.yaml").unwrap(); 147 | let mut server = Server::new(config.base); 148 | thread::spawn(move || { 149 | tcp_mock_server(); 150 | }); 151 | sleep(Duration::from_secs(1)); // wait for server to start 152 | thread::spawn(move || { 153 | let _ = server.run(); 154 | }); 155 | sleep(Duration::from_secs(1)); // wait for server to start 156 | 157 | // test TCP proxy 158 | let mut conn = TcpStream::connect("127.0.0.1:54500").await.unwrap(); 159 | let mut buf = [0u8; 5]; 160 | conn.write(b"hi").await.unwrap(); 161 | conn.read(&mut buf).await.unwrap(); 162 | assert_eq!(&buf, b"hello"); 163 | conn.shutdown().await.unwrap(); 164 | 165 | // test TCP echo 166 | let mut conn = TcpStream::connect("127.0.0.1:54956").await.unwrap(); 167 | let mut buf = [0u8; 1]; 168 | for i in 0..=10u8 { 169 | conn.write(&[i]).await.unwrap(); 170 | conn.read(&mut buf).await.unwrap(); 171 | assert_eq!(&buf, &[i]); 172 | } 173 | conn.shutdown().await.unwrap(); 174 | 175 | // test KCP echo 176 | let kcp_config = KcpConfig::default(); 177 | let server_addr: SocketAddr = "127.0.0.1:54959".parse().unwrap(); 178 | let mut conn = KcpStream::connect(&kcp_config, server_addr).await.unwrap(); 179 | let mut buf = [0u8; 1]; 180 | for i in 0..=10u8 { 181 | conn.write(&[i]).await.unwrap(); 182 | conn.read(&mut buf).await.unwrap(); 183 | assert_eq!(&buf, &[i]); 184 | } 185 | conn.shutdown().await.unwrap(); 186 | 187 | // test KCP proxy and close mock server 188 | let kcp_config = KcpConfig::default(); 189 | let server_addr: SocketAddr = "127.0.0.1:54958".parse().unwrap(); 190 | let mut conn = KcpStream::connect(&kcp_config, server_addr).await.unwrap(); 191 | let mut buf = [0u8; 5]; 192 | conn.write(b"by").await.unwrap(); 193 | conn.read(&mut buf).await.unwrap(); 194 | assert_eq!(&buf, b"hello"); 195 | conn.shutdown().await.unwrap(); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/servers/protocol/kcp.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Upstream; 2 | use crate::plugins::kcp::{KcpConfig, KcpListener, KcpStream}; 3 | use crate::servers::Proxy; 4 | use futures::future::try_join; 5 | use log::{debug, error, warn}; 6 | use std::net::SocketAddr; 7 | use std::sync::Arc; 8 | use tokio::io; 9 | use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; 10 | use tokio::net::TcpStream; 11 | 12 | pub async fn proxy(config: Arc) -> Result<(), Box> { 13 | let kcp_config = KcpConfig::default(); 14 | let mut listener = KcpListener::bind(kcp_config, config.listen).await?; 15 | let config = config.clone(); 16 | 17 | loop { 18 | let thread_proxy = config.clone(); 19 | match listener.accept().await { 20 | Err(err) => { 21 | error!("Failed to accept connection: {}", err); 22 | return Err(Box::new(err)); 23 | } 24 | Ok((stream, peer)) => { 25 | tokio::spawn(async move { 26 | match accept(stream, peer, thread_proxy).await { 27 | Ok(_) => {} 28 | Err(err) => { 29 | error!("Relay thread returned an error: {}", err); 30 | } 31 | }; 32 | }); 33 | } 34 | } 35 | } 36 | } 37 | 38 | async fn accept( 39 | inbound: KcpStream, 40 | peer: SocketAddr, 41 | proxy: Arc, 42 | ) -> Result<(), Box> { 43 | debug!("New connection from {:?}", peer); 44 | 45 | let upstream_name = proxy.default.clone(); 46 | 47 | debug!("Upstream: {}", upstream_name); 48 | 49 | let upstream = match proxy.upstream.get(&upstream_name) { 50 | Some(upstream) => upstream, 51 | None => { 52 | warn!( 53 | "No upstream named {:?} on server {:?}", 54 | proxy.default, proxy.name 55 | ); 56 | return process(inbound, proxy.upstream.get(&proxy.default).unwrap()).await; 57 | // ToDo: Remove unwrap and check default option 58 | } 59 | }; 60 | return process(inbound, upstream).await; 61 | } 62 | 63 | async fn process( 64 | mut inbound: KcpStream, 65 | upstream: &Upstream, 66 | ) -> Result<(), Box> { 67 | match upstream { 68 | Upstream::Ban => { 69 | let _ = inbound.shutdown(); 70 | } 71 | Upstream::Echo => { 72 | let (mut ri, mut wi) = io::split(inbound); 73 | let inbound_to_inbound = copy(&mut ri, &mut wi); 74 | let bytes_tx = inbound_to_inbound.await; 75 | debug!("Bytes read: {:?}", bytes_tx); 76 | } 77 | Upstream::Custom(custom) => match custom.protocol.as_ref() { 78 | "tcp" => { 79 | let outbound = TcpStream::connect(custom.addr.clone()).await?; 80 | 81 | let (mut ri, mut wi) = io::split(inbound); 82 | let (mut ro, mut wo) = io::split(outbound); 83 | 84 | let inbound_to_outbound = copy(&mut ri, &mut wo); 85 | let outbound_to_inbound = copy(&mut ro, &mut wi); 86 | 87 | let (bytes_tx, bytes_rx) = 88 | try_join(inbound_to_outbound, outbound_to_inbound).await?; 89 | 90 | debug!("Bytes read: {:?} write: {:?}", bytes_tx, bytes_rx); 91 | } 92 | _ => { 93 | error!("Reached unknown protocol: {:?}", custom.protocol); 94 | } 95 | }, 96 | }; 97 | Ok(()) 98 | } 99 | 100 | async fn copy<'a, R, W>(reader: &'a mut R, writer: &'a mut W) -> io::Result 101 | where 102 | R: AsyncRead + Unpin + ?Sized, 103 | W: AsyncWrite + Unpin + ?Sized, 104 | { 105 | match io::copy(reader, writer).await { 106 | Ok(u64) => { 107 | let _ = writer.shutdown().await; 108 | Ok(u64) 109 | } 110 | Err(_) => Ok(0), 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/servers/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod kcp; 2 | pub mod tcp; 3 | pub mod tls; 4 | -------------------------------------------------------------------------------- /src/servers/protocol/tcp.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Upstream; 2 | use crate::servers::protocol::tls::get_sni; 3 | use crate::servers::Proxy; 4 | use futures::future::try_join; 5 | use log::{debug, error, warn}; 6 | use std::sync::Arc; 7 | use tokio::io; 8 | use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; 9 | use tokio::net::{TcpListener, TcpStream}; 10 | 11 | pub async fn proxy(config: Arc) -> Result<(), Box> { 12 | let listener = TcpListener::bind(config.listen).await?; 13 | let config = config.clone(); 14 | 15 | loop { 16 | let thread_proxy = config.clone(); 17 | match listener.accept().await { 18 | Err(err) => { 19 | error!("Failed to accept connection: {}", err); 20 | return Err(Box::new(err)); 21 | } 22 | Ok((stream, _)) => { 23 | tokio::spawn(async move { 24 | match accept(stream, thread_proxy).await { 25 | Ok(_) => {} 26 | Err(err) => { 27 | error!("Relay thread returned an error: {}", err); 28 | } 29 | }; 30 | }); 31 | } 32 | } 33 | } 34 | } 35 | 36 | async fn accept(inbound: TcpStream, proxy: Arc) -> Result<(), Box> { 37 | debug!("New connection from {:?}", inbound.peer_addr()?); 38 | 39 | let upstream_name = match proxy.tls { 40 | false => proxy.default.clone(), 41 | true => { 42 | let mut hello_buf = [0u8; 1024]; 43 | inbound.peek(&mut hello_buf).await?; 44 | let snis = get_sni(&hello_buf); 45 | if snis.is_empty() { 46 | proxy.default.clone() 47 | } else { 48 | match proxy.sni.clone() { 49 | Some(sni_map) => { 50 | let mut upstream = proxy.default.clone(); 51 | for sni in snis { 52 | let m = sni_map.get(&sni); 53 | if m.is_some() { 54 | upstream = m.unwrap().clone(); 55 | break; 56 | } 57 | } 58 | upstream 59 | } 60 | None => proxy.default.clone(), 61 | } 62 | } 63 | } 64 | }; 65 | 66 | debug!("Upstream: {}", upstream_name); 67 | 68 | let upstream = match proxy.upstream.get(&upstream_name) { 69 | Some(upstream) => upstream, 70 | None => { 71 | warn!( 72 | "No upstream named {:?} on server {:?}", 73 | proxy.default, proxy.name 74 | ); 75 | return process(inbound, proxy.upstream.get(&proxy.default).unwrap()).await; 76 | // ToDo: Remove unwrap and check default option 77 | } 78 | }; 79 | return process(inbound, upstream).await; 80 | } 81 | 82 | async fn process( 83 | mut inbound: TcpStream, 84 | upstream: &Upstream, 85 | ) -> Result<(), Box> { 86 | match upstream { 87 | Upstream::Ban => { 88 | let _ = inbound.shutdown(); 89 | } 90 | Upstream::Echo => { 91 | let (mut ri, mut wi) = io::split(inbound); 92 | let inbound_to_inbound = copy(&mut ri, &mut wi); 93 | let bytes_tx = inbound_to_inbound.await; 94 | debug!("Bytes read: {:?}", bytes_tx); 95 | } 96 | Upstream::Custom(custom) => match custom.protocol.as_ref() { 97 | "tcp" => { 98 | let outbound = TcpStream::connect(custom.addr.clone()).await?; 99 | 100 | let (mut ri, mut wi) = io::split(inbound); 101 | let (mut ro, mut wo) = io::split(outbound); 102 | 103 | let inbound_to_outbound = copy(&mut ri, &mut wo); 104 | let outbound_to_inbound = copy(&mut ro, &mut wi); 105 | 106 | let (bytes_tx, bytes_rx) = 107 | try_join(inbound_to_outbound, outbound_to_inbound).await?; 108 | 109 | debug!("Bytes read: {:?} write: {:?}", bytes_tx, bytes_rx); 110 | } 111 | _ => { 112 | error!("Reached unknown protocol: {:?}", custom.protocol); 113 | } 114 | }, 115 | }; 116 | Ok(()) 117 | } 118 | 119 | async fn copy<'a, R, W>(reader: &'a mut R, writer: &'a mut W) -> io::Result 120 | where 121 | R: AsyncRead + Unpin + ?Sized, 122 | W: AsyncWrite + Unpin + ?Sized, 123 | { 124 | match io::copy(reader, writer).await { 125 | Ok(u64) => { 126 | let _ = writer.shutdown().await; 127 | Ok(u64) 128 | } 129 | Err(_) => Ok(0), 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/servers/protocol/tls.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, warn}; 2 | use tls_parser::{ 3 | parse_tls_extensions, parse_tls_raw_record, parse_tls_record_with_header, TlsMessage, 4 | TlsMessageHandshake, 5 | }; 6 | 7 | pub fn get_sni(buf: &[u8]) -> Vec { 8 | let mut snis: Vec = Vec::new(); 9 | match parse_tls_raw_record(buf) { 10 | Ok((_, ref r)) => match parse_tls_record_with_header(r.data, &r.hdr) { 11 | Ok((_, ref msg_list)) => { 12 | for msg in msg_list { 13 | if let TlsMessage::Handshake(TlsMessageHandshake::ClientHello(ref content)) = 14 | *msg 15 | { 16 | debug!("TLS ClientHello version: {}", content.version); 17 | let ext = parse_tls_extensions(content.ext.unwrap_or(b"")); 18 | match ext { 19 | Ok((_, ref extensions)) => { 20 | for ext in extensions { 21 | if let tls_parser::TlsExtension::SNI(ref v) = *ext { 22 | for &(t, sni) in v { 23 | match String::from_utf8(sni.to_vec()) { 24 | Ok(s) => { 25 | debug!("TLS SNI: {} {}", t, s); 26 | snis.push(s); 27 | } 28 | Err(e) => { 29 | warn!("Failed to parse SNI: {} {}", t, e); 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | Err(e) => { 37 | warn!("TLS extensions error: {}", e); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | Err(err) => { 44 | warn!("Failed to parse TLS: {}", err); 45 | } 46 | }, 47 | Err(err) => { 48 | warn!("Failed to parse TLS: {}", err); 49 | } 50 | } 51 | 52 | snis 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn test_sni_extract() { 61 | const BUF: [u8; 517] = [ 62 | 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0x35, 0x7a, 0xba, 63 | 0x3d, 0x89, 0xd2, 0x5e, 0x7a, 0xa2, 0xd4, 0xe5, 0x6d, 0xd5, 0xa3, 0x98, 0x41, 0xb0, 64 | 0xae, 0x41, 0xfc, 0xe6, 0x64, 0xfd, 0xae, 0x0b, 0x27, 0x6d, 0x90, 0xa8, 0x0a, 0xfa, 65 | 0x90, 0x20, 0x59, 0x6f, 0x13, 0x18, 0x4a, 0xd1, 0x1c, 0xc4, 0x83, 0x8c, 0xfc, 0x93, 66 | 0xac, 0x6b, 0x3b, 0xac, 0x67, 0xd0, 0x36, 0xb0, 0xa2, 0x1b, 0x04, 0xf7, 0xde, 0x02, 67 | 0xfb, 0x96, 0x1e, 0xdc, 0x76, 0xa8, 0x00, 0x20, 0x2a, 0x2a, 0x13, 0x01, 0x13, 0x02, 68 | 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 69 | 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 70 | 0x01, 0x93, 0xea, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x11, 0x00, 0x00, 71 | 0x0e, 0x77, 0x77, 0x77, 0x2e, 0x6c, 0x69, 0x72, 0x75, 0x69, 0x2e, 0x74, 0x65, 0x63, 72 | 0x68, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 73 | 0x00, 0x08, 0xba, 0xba, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x02, 74 | 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 75 | 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 76 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 77 | 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x00, 78 | 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0xba, 0xba, 0x00, 0x01, 0x00, 79 | 0x00, 0x1d, 0x00, 0x20, 0x3b, 0x45, 0xf9, 0xbc, 0x6e, 0x23, 0x86, 0x41, 0xa5, 0xb2, 80 | 0xf5, 0x03, 0xec, 0x67, 0x4a, 0xd7, 0x9a, 0x17, 0x9f, 0x0c, 0x38, 0x6d, 0x36, 0xf3, 81 | 0x4e, 0x5d, 0xa4, 0x7d, 0x15, 0x79, 0xa4, 0x3f, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 82 | 0x00, 0x2b, 0x00, 0x0b, 0x0a, 0xba, 0xba, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 83 | 0x01, 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02, 0x44, 0x69, 0x00, 0x05, 0x00, 0x03, 84 | 0x02, 0x68, 0x32, 0xda, 0xda, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00, 0xc5, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 92 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 94 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 95 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 96 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 97 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 98 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 99 | ]; 100 | let sni = get_sni(&BUF); 101 | assert!(sni[0] == "www.lirui.tech".to_string()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests/config.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | log: disable 3 | 4 | servers: 5 | test_server: 6 | listen: 7 | - "0.0.0.0:21341" 8 | - "[::]:21341" 9 | tls: true 10 | sni: 11 | proxy.test.com: proxy 12 | www.test.com: web 13 | default: ban 14 | tcp_server: 15 | listen: 16 | - "127.0.0.1:54500" 17 | default: tester 18 | tcp_echo_server: 19 | listen: 20 | - "0.0.0.0:54956" 21 | default: echo 22 | kcp_server: 23 | protocol: kcp 24 | listen: 25 | - "127.0.0.1:54958" 26 | default: tester 27 | kcp_echo_server: 28 | protocol: kcp 29 | listen: 30 | - "127.0.0.1:54959" 31 | default: echo 32 | 33 | upstream: 34 | web: "tcp://127.0.0.1:8080" 35 | proxy: "tcp://www.example.com:1024" 36 | tester: "tcp://127.0.0.1:54599" --------------------------------------------------------------------------------