├── .github ├── dependabot.yml └── workflows │ ├── docker.yml │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── docker ├── .dockerignore ├── Dockerfile ├── README.md ├── docker-compose.yml └── phantun.sh ├── fake-tcp ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src │ ├── lib.rs │ └── packet.rs ├── images ├── packet-headers.png ├── phantun-vs-udp2raw-benchmark-result.png └── traffic-flow.png └── phantun ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── bin ├── client.rs └── server.rs ├── lib.rs └── utils.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | 9 | - package-ecosystem: "cargo" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker image build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-22.04 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup QEMU 17 | uses: docker/setup-qemu-action@v3 18 | with: 19 | platforms: linux/amd64 20 | 21 | - name: Setup Docker Buildx 22 | uses: docker/setup-buildx-action@v3 23 | 24 | - name: Build Docker Image 25 | uses: docker/build-push-action@v6 26 | with: 27 | context: . 28 | file: docker/Dockerfile 29 | tags: phantun 30 | platforms: linux/amd64 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build Releases 2 | on: 3 | push: 4 | tags: 5 | - v*.*.* 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build-cross: 12 | runs-on: ubuntu-latest 13 | env: 14 | RUST_BACKTRACE: full 15 | strategy: 16 | matrix: 17 | target: 18 | - x86_64-unknown-linux-gnu 19 | - x86_64-unknown-linux-musl 20 | - i686-unknown-linux-gnu 21 | - i686-unknown-linux-musl 22 | - armv7-unknown-linux-gnueabihf 23 | - armv7-unknown-linux-musleabihf 24 | - arm-unknown-linux-gnueabihf 25 | - arm-unknown-linux-musleabihf 26 | - aarch64-unknown-linux-gnu 27 | - aarch64-unknown-linux-musl 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions-rs/toolchain@v1 32 | with: 33 | toolchain: stable 34 | target: ${{ matrix.target }} 35 | override: true 36 | - uses: actions-rs/cargo@v1 37 | with: 38 | use-cross: true 39 | command: build 40 | args: --release --target ${{ matrix.target }} 41 | - name: Rename artifacts and compress 42 | run: | 43 | cd target/${{ matrix.target }}/release 44 | mv client phantun_client 45 | mv server phantun_server 46 | zip phantun_${{ matrix.target }}.zip phantun_client phantun_server 47 | 48 | - name: Upload Github Assets 49 | uses: softprops/action-gh-release@v2 50 | with: 51 | files: target/${{ matrix.target }}/release/*.zip 52 | prerelease: ${{ contains(github.ref, '-') }} 53 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | - name: Run lint 19 | run: cargo clippy --verbose 20 | - name: Build 21 | run: cargo build --verbose 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys 0.59.0", 77 | ] 78 | 79 | [[package]] 80 | name = "autocfg" 81 | version = "1.4.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 84 | 85 | [[package]] 86 | name = "backtrace" 87 | version = "0.3.74" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 90 | dependencies = [ 91 | "addr2line", 92 | "cfg-if", 93 | "libc", 94 | "miniz_oxide", 95 | "object", 96 | "rustc-demangle", 97 | "windows-targets", 98 | ] 99 | 100 | [[package]] 101 | name = "bitflags" 102 | version = "2.9.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 105 | 106 | [[package]] 107 | name = "bumpalo" 108 | version = "3.17.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 111 | 112 | [[package]] 113 | name = "byteorder" 114 | version = "1.5.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 117 | 118 | [[package]] 119 | name = "bytes" 120 | version = "1.10.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 123 | 124 | [[package]] 125 | name = "cfg-if" 126 | version = "1.0.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 129 | 130 | [[package]] 131 | name = "cfg_aliases" 132 | version = "0.2.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 135 | 136 | [[package]] 137 | name = "clap" 138 | version = "4.5.32" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" 141 | dependencies = [ 142 | "clap_builder", 143 | ] 144 | 145 | [[package]] 146 | name = "clap_builder" 147 | version = "4.5.32" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" 150 | dependencies = [ 151 | "anstream", 152 | "anstyle", 153 | "clap_lex", 154 | "strsim", 155 | ] 156 | 157 | [[package]] 158 | name = "clap_lex" 159 | version = "0.7.4" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 162 | 163 | [[package]] 164 | name = "colorchoice" 165 | version = "1.0.3" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 168 | 169 | [[package]] 170 | name = "either" 171 | version = "1.15.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 174 | 175 | [[package]] 176 | name = "env_logger" 177 | version = "0.10.2" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 180 | dependencies = [ 181 | "humantime", 182 | "is-terminal", 183 | "log", 184 | "regex", 185 | "termcolor", 186 | ] 187 | 188 | [[package]] 189 | name = "fake-tcp" 190 | version = "0.6.0" 191 | dependencies = [ 192 | "bytes", 193 | "flume", 194 | "internet-checksum", 195 | "log", 196 | "pnet", 197 | "rand", 198 | "tokio", 199 | "tokio-tun", 200 | ] 201 | 202 | [[package]] 203 | name = "flume" 204 | version = "0.11.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" 207 | dependencies = [ 208 | "futures-core", 209 | "futures-sink", 210 | "nanorand", 211 | "spin", 212 | ] 213 | 214 | [[package]] 215 | name = "futures-core" 216 | version = "0.3.31" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 219 | 220 | [[package]] 221 | name = "futures-sink" 222 | version = "0.3.31" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 225 | 226 | [[package]] 227 | name = "getrandom" 228 | version = "0.2.15" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 231 | dependencies = [ 232 | "cfg-if", 233 | "js-sys", 234 | "libc", 235 | "wasi 0.11.0+wasi-snapshot-preview1", 236 | "wasm-bindgen", 237 | ] 238 | 239 | [[package]] 240 | name = "getrandom" 241 | version = "0.3.1" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 244 | dependencies = [ 245 | "cfg-if", 246 | "libc", 247 | "wasi 0.13.3+wasi-0.2.2", 248 | "windows-targets", 249 | ] 250 | 251 | [[package]] 252 | name = "gimli" 253 | version = "0.31.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 256 | 257 | [[package]] 258 | name = "glob" 259 | version = "0.3.2" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 262 | 263 | [[package]] 264 | name = "hermit-abi" 265 | version = "0.3.9" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 268 | 269 | [[package]] 270 | name = "hermit-abi" 271 | version = "0.5.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" 274 | 275 | [[package]] 276 | name = "humantime" 277 | version = "2.2.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" 280 | 281 | [[package]] 282 | name = "internet-checksum" 283 | version = "0.2.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "fc6d6206008e25125b1f97fbe5d309eb7b85141cf9199d52dbd3729a1584dd16" 286 | 287 | [[package]] 288 | name = "ipnetwork" 289 | version = "0.20.0" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" 292 | dependencies = [ 293 | "serde", 294 | ] 295 | 296 | [[package]] 297 | name = "is-terminal" 298 | version = "0.4.16" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" 301 | dependencies = [ 302 | "hermit-abi 0.5.0", 303 | "libc", 304 | "windows-sys 0.59.0", 305 | ] 306 | 307 | [[package]] 308 | name = "is_terminal_polyfill" 309 | version = "1.70.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 312 | 313 | [[package]] 314 | name = "js-sys" 315 | version = "0.3.77" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 318 | dependencies = [ 319 | "once_cell", 320 | "wasm-bindgen", 321 | ] 322 | 323 | [[package]] 324 | name = "libc" 325 | version = "0.2.171" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 328 | 329 | [[package]] 330 | name = "lock_api" 331 | version = "0.4.12" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 334 | dependencies = [ 335 | "autocfg", 336 | "scopeguard", 337 | ] 338 | 339 | [[package]] 340 | name = "log" 341 | version = "0.4.26" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 344 | 345 | [[package]] 346 | name = "memchr" 347 | version = "2.7.4" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 350 | 351 | [[package]] 352 | name = "memoffset" 353 | version = "0.9.1" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 356 | dependencies = [ 357 | "autocfg", 358 | ] 359 | 360 | [[package]] 361 | name = "miniz_oxide" 362 | version = "0.8.5" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" 365 | dependencies = [ 366 | "adler2", 367 | ] 368 | 369 | [[package]] 370 | name = "mio" 371 | version = "1.0.3" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 374 | dependencies = [ 375 | "libc", 376 | "wasi 0.11.0+wasi-snapshot-preview1", 377 | "windows-sys 0.52.0", 378 | ] 379 | 380 | [[package]] 381 | name = "nanorand" 382 | version = "0.7.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 385 | dependencies = [ 386 | "getrandom 0.2.15", 387 | ] 388 | 389 | [[package]] 390 | name = "neli" 391 | version = "0.6.5" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "93062a0dce6da2517ea35f301dfc88184ce18d3601ec786a727a87bf535deca9" 394 | dependencies = [ 395 | "byteorder", 396 | "libc", 397 | "log", 398 | "neli-proc-macros", 399 | ] 400 | 401 | [[package]] 402 | name = "neli-proc-macros" 403 | version = "0.1.4" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "0c8034b7fbb6f9455b2a96c19e6edf8dc9fc34c70449938d8ee3b4df363f61fe" 406 | dependencies = [ 407 | "either", 408 | "proc-macro2", 409 | "quote", 410 | "serde", 411 | "syn 1.0.109", 412 | ] 413 | 414 | [[package]] 415 | name = "nix" 416 | version = "0.29.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 419 | dependencies = [ 420 | "bitflags", 421 | "cfg-if", 422 | "cfg_aliases", 423 | "libc", 424 | "memoffset", 425 | ] 426 | 427 | [[package]] 428 | name = "no-std-net" 429 | version = "0.6.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" 432 | 433 | [[package]] 434 | name = "num_cpus" 435 | version = "1.16.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 438 | dependencies = [ 439 | "hermit-abi 0.3.9", 440 | "libc", 441 | ] 442 | 443 | [[package]] 444 | name = "object" 445 | version = "0.36.7" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 448 | dependencies = [ 449 | "memchr", 450 | ] 451 | 452 | [[package]] 453 | name = "once_cell" 454 | version = "1.21.1" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" 457 | 458 | [[package]] 459 | name = "parking_lot" 460 | version = "0.12.3" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 463 | dependencies = [ 464 | "lock_api", 465 | "parking_lot_core", 466 | ] 467 | 468 | [[package]] 469 | name = "parking_lot_core" 470 | version = "0.9.10" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 473 | dependencies = [ 474 | "cfg-if", 475 | "libc", 476 | "redox_syscall", 477 | "smallvec", 478 | "windows-targets", 479 | ] 480 | 481 | [[package]] 482 | name = "phantun" 483 | version = "0.7.0" 484 | dependencies = [ 485 | "clap", 486 | "fake-tcp", 487 | "log", 488 | "neli", 489 | "nix", 490 | "num_cpus", 491 | "pretty_env_logger", 492 | "socket2", 493 | "tokio", 494 | "tokio-tun", 495 | "tokio-util", 496 | ] 497 | 498 | [[package]] 499 | name = "pin-project-lite" 500 | version = "0.2.16" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 503 | 504 | [[package]] 505 | name = "pnet" 506 | version = "0.35.0" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d" 509 | dependencies = [ 510 | "ipnetwork", 511 | "pnet_base", 512 | "pnet_datalink", 513 | "pnet_packet", 514 | "pnet_sys", 515 | "pnet_transport", 516 | ] 517 | 518 | [[package]] 519 | name = "pnet_base" 520 | version = "0.35.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" 523 | dependencies = [ 524 | "no-std-net", 525 | ] 526 | 527 | [[package]] 528 | name = "pnet_datalink" 529 | version = "0.35.0" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" 532 | dependencies = [ 533 | "ipnetwork", 534 | "libc", 535 | "pnet_base", 536 | "pnet_sys", 537 | "winapi", 538 | ] 539 | 540 | [[package]] 541 | name = "pnet_macros" 542 | version = "0.35.0" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863" 545 | dependencies = [ 546 | "proc-macro2", 547 | "quote", 548 | "regex", 549 | "syn 2.0.100", 550 | ] 551 | 552 | [[package]] 553 | name = "pnet_macros_support" 554 | version = "0.35.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab" 557 | dependencies = [ 558 | "pnet_base", 559 | ] 560 | 561 | [[package]] 562 | name = "pnet_packet" 563 | version = "0.35.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f" 566 | dependencies = [ 567 | "glob", 568 | "pnet_base", 569 | "pnet_macros", 570 | "pnet_macros_support", 571 | ] 572 | 573 | [[package]] 574 | name = "pnet_sys" 575 | version = "0.35.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" 578 | dependencies = [ 579 | "libc", 580 | "winapi", 581 | ] 582 | 583 | [[package]] 584 | name = "pnet_transport" 585 | version = "0.35.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf" 588 | dependencies = [ 589 | "libc", 590 | "pnet_base", 591 | "pnet_packet", 592 | "pnet_sys", 593 | ] 594 | 595 | [[package]] 596 | name = "ppv-lite86" 597 | version = "0.2.21" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 600 | dependencies = [ 601 | "zerocopy", 602 | ] 603 | 604 | [[package]] 605 | name = "pretty_env_logger" 606 | version = "0.5.0" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" 609 | dependencies = [ 610 | "env_logger", 611 | "log", 612 | ] 613 | 614 | [[package]] 615 | name = "proc-macro2" 616 | version = "1.0.94" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 619 | dependencies = [ 620 | "unicode-ident", 621 | ] 622 | 623 | [[package]] 624 | name = "quote" 625 | version = "1.0.40" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 628 | dependencies = [ 629 | "proc-macro2", 630 | ] 631 | 632 | [[package]] 633 | name = "rand" 634 | version = "0.9.0" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" 637 | dependencies = [ 638 | "rand_chacha", 639 | "rand_core", 640 | "zerocopy", 641 | ] 642 | 643 | [[package]] 644 | name = "rand_chacha" 645 | version = "0.9.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 648 | dependencies = [ 649 | "ppv-lite86", 650 | "rand_core", 651 | ] 652 | 653 | [[package]] 654 | name = "rand_core" 655 | version = "0.9.3" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 658 | dependencies = [ 659 | "getrandom 0.3.1", 660 | ] 661 | 662 | [[package]] 663 | name = "redox_syscall" 664 | version = "0.5.10" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" 667 | dependencies = [ 668 | "bitflags", 669 | ] 670 | 671 | [[package]] 672 | name = "regex" 673 | version = "1.11.1" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 676 | dependencies = [ 677 | "aho-corasick", 678 | "memchr", 679 | "regex-automata", 680 | "regex-syntax", 681 | ] 682 | 683 | [[package]] 684 | name = "regex-automata" 685 | version = "0.4.9" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 688 | dependencies = [ 689 | "aho-corasick", 690 | "memchr", 691 | "regex-syntax", 692 | ] 693 | 694 | [[package]] 695 | name = "regex-syntax" 696 | version = "0.8.5" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 699 | 700 | [[package]] 701 | name = "rustc-demangle" 702 | version = "0.1.24" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 705 | 706 | [[package]] 707 | name = "scopeguard" 708 | version = "1.2.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 711 | 712 | [[package]] 713 | name = "serde" 714 | version = "1.0.219" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 717 | dependencies = [ 718 | "serde_derive", 719 | ] 720 | 721 | [[package]] 722 | name = "serde_derive" 723 | version = "1.0.219" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 726 | dependencies = [ 727 | "proc-macro2", 728 | "quote", 729 | "syn 2.0.100", 730 | ] 731 | 732 | [[package]] 733 | name = "signal-hook-registry" 734 | version = "1.4.2" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 737 | dependencies = [ 738 | "libc", 739 | ] 740 | 741 | [[package]] 742 | name = "smallvec" 743 | version = "1.14.0" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 746 | 747 | [[package]] 748 | name = "socket2" 749 | version = "0.5.8" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 752 | dependencies = [ 753 | "libc", 754 | "windows-sys 0.52.0", 755 | ] 756 | 757 | [[package]] 758 | name = "spin" 759 | version = "0.9.8" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 762 | dependencies = [ 763 | "lock_api", 764 | ] 765 | 766 | [[package]] 767 | name = "strsim" 768 | version = "0.11.1" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 771 | 772 | [[package]] 773 | name = "syn" 774 | version = "1.0.109" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 777 | dependencies = [ 778 | "proc-macro2", 779 | "quote", 780 | "unicode-ident", 781 | ] 782 | 783 | [[package]] 784 | name = "syn" 785 | version = "2.0.100" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 788 | dependencies = [ 789 | "proc-macro2", 790 | "quote", 791 | "unicode-ident", 792 | ] 793 | 794 | [[package]] 795 | name = "termcolor" 796 | version = "1.4.1" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 799 | dependencies = [ 800 | "winapi-util", 801 | ] 802 | 803 | [[package]] 804 | name = "thiserror" 805 | version = "2.0.12" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 808 | dependencies = [ 809 | "thiserror-impl", 810 | ] 811 | 812 | [[package]] 813 | name = "thiserror-impl" 814 | version = "2.0.12" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 817 | dependencies = [ 818 | "proc-macro2", 819 | "quote", 820 | "syn 2.0.100", 821 | ] 822 | 823 | [[package]] 824 | name = "tokio" 825 | version = "1.44.1" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" 828 | dependencies = [ 829 | "backtrace", 830 | "bytes", 831 | "libc", 832 | "mio", 833 | "parking_lot", 834 | "pin-project-lite", 835 | "signal-hook-registry", 836 | "socket2", 837 | "tokio-macros", 838 | "windows-sys 0.52.0", 839 | ] 840 | 841 | [[package]] 842 | name = "tokio-macros" 843 | version = "2.5.0" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 846 | dependencies = [ 847 | "proc-macro2", 848 | "quote", 849 | "syn 2.0.100", 850 | ] 851 | 852 | [[package]] 853 | name = "tokio-tun" 854 | version = "0.13.2" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "be25a316a2d10d0ddd46de9ddd73cdb4262678e1e52f4a32a31421f1e2b816b4" 857 | dependencies = [ 858 | "libc", 859 | "nix", 860 | "thiserror", 861 | "tokio", 862 | ] 863 | 864 | [[package]] 865 | name = "tokio-util" 866 | version = "0.7.14" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" 869 | dependencies = [ 870 | "bytes", 871 | "futures-core", 872 | "futures-sink", 873 | "pin-project-lite", 874 | "tokio", 875 | ] 876 | 877 | [[package]] 878 | name = "unicode-ident" 879 | version = "1.0.18" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 882 | 883 | [[package]] 884 | name = "utf8parse" 885 | version = "0.2.2" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 888 | 889 | [[package]] 890 | name = "wasi" 891 | version = "0.11.0+wasi-snapshot-preview1" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 894 | 895 | [[package]] 896 | name = "wasi" 897 | version = "0.13.3+wasi-0.2.2" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 900 | dependencies = [ 901 | "wit-bindgen-rt", 902 | ] 903 | 904 | [[package]] 905 | name = "wasm-bindgen" 906 | version = "0.2.100" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 909 | dependencies = [ 910 | "cfg-if", 911 | "once_cell", 912 | "wasm-bindgen-macro", 913 | ] 914 | 915 | [[package]] 916 | name = "wasm-bindgen-backend" 917 | version = "0.2.100" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 920 | dependencies = [ 921 | "bumpalo", 922 | "log", 923 | "proc-macro2", 924 | "quote", 925 | "syn 2.0.100", 926 | "wasm-bindgen-shared", 927 | ] 928 | 929 | [[package]] 930 | name = "wasm-bindgen-macro" 931 | version = "0.2.100" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 934 | dependencies = [ 935 | "quote", 936 | "wasm-bindgen-macro-support", 937 | ] 938 | 939 | [[package]] 940 | name = "wasm-bindgen-macro-support" 941 | version = "0.2.100" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 944 | dependencies = [ 945 | "proc-macro2", 946 | "quote", 947 | "syn 2.0.100", 948 | "wasm-bindgen-backend", 949 | "wasm-bindgen-shared", 950 | ] 951 | 952 | [[package]] 953 | name = "wasm-bindgen-shared" 954 | version = "0.2.100" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 957 | dependencies = [ 958 | "unicode-ident", 959 | ] 960 | 961 | [[package]] 962 | name = "winapi" 963 | version = "0.3.9" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 966 | dependencies = [ 967 | "winapi-i686-pc-windows-gnu", 968 | "winapi-x86_64-pc-windows-gnu", 969 | ] 970 | 971 | [[package]] 972 | name = "winapi-i686-pc-windows-gnu" 973 | version = "0.4.0" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 976 | 977 | [[package]] 978 | name = "winapi-util" 979 | version = "0.1.9" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 982 | dependencies = [ 983 | "windows-sys 0.59.0", 984 | ] 985 | 986 | [[package]] 987 | name = "winapi-x86_64-pc-windows-gnu" 988 | version = "0.4.0" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 991 | 992 | [[package]] 993 | name = "windows-sys" 994 | version = "0.52.0" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 997 | dependencies = [ 998 | "windows-targets", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "windows-sys" 1003 | version = "0.59.0" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1006 | dependencies = [ 1007 | "windows-targets", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "windows-targets" 1012 | version = "0.52.6" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1015 | dependencies = [ 1016 | "windows_aarch64_gnullvm", 1017 | "windows_aarch64_msvc", 1018 | "windows_i686_gnu", 1019 | "windows_i686_gnullvm", 1020 | "windows_i686_msvc", 1021 | "windows_x86_64_gnu", 1022 | "windows_x86_64_gnullvm", 1023 | "windows_x86_64_msvc", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "windows_aarch64_gnullvm" 1028 | version = "0.52.6" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1031 | 1032 | [[package]] 1033 | name = "windows_aarch64_msvc" 1034 | version = "0.52.6" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1037 | 1038 | [[package]] 1039 | name = "windows_i686_gnu" 1040 | version = "0.52.6" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1043 | 1044 | [[package]] 1045 | name = "windows_i686_gnullvm" 1046 | version = "0.52.6" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1049 | 1050 | [[package]] 1051 | name = "windows_i686_msvc" 1052 | version = "0.52.6" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1055 | 1056 | [[package]] 1057 | name = "windows_x86_64_gnu" 1058 | version = "0.52.6" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1061 | 1062 | [[package]] 1063 | name = "windows_x86_64_gnullvm" 1064 | version = "0.52.6" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1067 | 1068 | [[package]] 1069 | name = "windows_x86_64_msvc" 1070 | version = "0.52.6" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1073 | 1074 | [[package]] 1075 | name = "wit-bindgen-rt" 1076 | version = "0.33.0" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 1079 | dependencies = [ 1080 | "bitflags", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "zerocopy" 1085 | version = "0.8.23" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" 1088 | dependencies = [ 1089 | "zerocopy-derive", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "zerocopy-derive" 1094 | version = "0.8.23" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" 1097 | dependencies = [ 1098 | "proc-macro2", 1099 | "quote", 1100 | "syn 2.0.100", 1101 | ] 1102 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | resolver = "2" 4 | 5 | members = [ 6 | "fake-tcp", 7 | "phantun", 8 | ] 9 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 2021-2024 Datong Sun (dndx@idndx.com) 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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2024 Datong Sun (dndx@idndx.com) 4 | 5 | Permission is hereby granted, free of charge, to any 6 | person obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the 8 | Software without restriction, including without 9 | limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software 12 | is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions 17 | of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phantun 2 | 3 | A lightweight and fast UDP to TCP obfuscator. 4 | 5 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dndx/phantun/rust.yml) 6 | ![docs.rs](https://img.shields.io/docsrs/fake-tcp) 7 | 8 | Table of Contents 9 | ================= 10 | 11 | * [Phantun](#phantun) 12 | * [Latest release](#latest-release) 13 | * [Overview](#overview) 14 | * [Usage](#usage) 15 | * [1. Enable Kernel IP forwarding](#1-enable-kernel-ip-forwarding) 16 | * [2. Add required firewall rules](#2-add-required-firewall-rules) 17 | * [Client](#client) 18 | * [Using nftables](#using-nftables) 19 | * [Using iptables](#using-iptables) 20 | * [Server](#server) 21 | * [Using nftables](#using-nftables) 22 | * [Using iptables](#using-iptables) 23 | * [3. Run Phantun binaries as non-root (Optional)](#3-run-phantun-binaries-as-non-root-optional) 24 | * [4. Start Phantun daemon](#4-start-phantun-daemon) 25 | * [Server](#server) 26 | * [Client](#client) 27 | * [MTU overhead](#mtu-overhead) 28 | * [MTU calculation for WireGuard](#mtu-calculation-for-wireguard) 29 | * [Version compatibility](#version-compatibility) 30 | * [Documentations](#documentations) 31 | * [Performance](#performance) 32 | * [Future plans](#future-plans) 33 | * [Compariation to udp2raw](#compariation-to-udp2raw) 34 | * [License](#license) 35 | 36 | # Latest release 37 | 38 | [v0.7.0](https://github.com/dndx/phantun/releases/tag/v0.7.0) 39 | 40 | # Overview 41 | 42 | Phantun is a project that obfuscated UDP packets into TCP connections. It aims to 43 | achieve maximum performance with minimum processing and encapsulation overhead. 44 | 45 | It is commonly used in environments where UDP is blocked/throttled but TCP is allowed through. 46 | 47 | Phantun simply converts a stream of UDP packets into obfuscated TCP stream packets. The TCP stack 48 | used by Phantun is designed to pass through most L3/L4 stateful/stateless firewalls/NAT 49 | devices. It will **not** be able to pass through L7 proxies. 50 | However, the advantage of this approach is that none of the common UDP over TCP performance killer 51 | such as retransmissions and flow control will occur. The underlying UDP properties such as 52 | out-of-order delivery are fully preserved even if the connection ends up looking like a TCP 53 | connection from the perspective of firewalls/NAT devices. 54 | 55 | Phantun means Phantom TUN, as it is an obfuscator for UDP traffic that does just enough work 56 | to make it pass through stateful firewall/NATs as TCP packets. 57 | 58 | Phantun is written in 100% safe Rust. It has been optimized extensively to scale well on multi-core 59 | systems and has no issue saturating all available CPU resources on a fast connection. 60 | See the [Performance](#performance) section for benchmarking results. 61 | 62 | ![Phantun benchmark results](images/phantun-vs-udp2raw-benchmark-result.png) 63 | ![Traffic flow diagram](images/traffic-flow.png) 64 | 65 | # Usage 66 | 67 | For the example below, it is assumed that **Phantun Server** listens for incoming Phantun Client connections at 68 | port `4567` (the `--local` option for server), and it forwards UDP packets to UDP server at `127.0.0.1:1234` 69 | (the `--remote` option for server). 70 | 71 | It is also assumed that **Phantun Client** listens for incoming UDP packets at 72 | `127.0.0.1:1234` (the `--local` option for client) and connects to Phantun Server at `10.0.0.1:4567` 73 | (the `--remote` option for client). 74 | 75 | Phantun creates TUN interface for both the Client and Server. For **Client**, Phantun assigns itself the IP address 76 | `192.168.200.2` and `fcc8::2` by default. 77 | For **Server**, it assigns `192.168.201.2` and `fcc9::2` by default. Therefore, your Kernel must have 78 | IPv4/IPv6 forwarding enabled and setup appropriate iptables/nftables rules for NAT between your physical 79 | NIC address and Phantun's Tun interface address. 80 | 81 | You may customize the name of Tun interface created by Phantun and the assigned addresses. Please 82 | run the executable with `-h` options to see how to change them. 83 | 84 | Another way to help understand this network topology (please see the diagram above for an illustration of this topology): 85 | 86 | Phantun Client is like a machine with private IP address (`192.168.200.2`/`fcc8::2`) behind a router. 87 | In order for it to reach the Internet, you will need to SNAT the private IP address before it's traffic 88 | leaves the NIC. 89 | 90 | Phantun Server is like a server with private IP address (`192.168.201.2`/`fcc9::2`) behind a router. 91 | In order to access it from the Internet, you need to `DNAT` it's listening port on the router 92 | and change the destination IP address to where the server is listening for incoming connections. 93 | 94 | In those cases, the machine/iptables running Phantun acts as the "router" that allows Phantun 95 | to communicate with outside using it's private IP addresses. 96 | 97 | As of Phantun v0.4.1, IPv6 is fully supported for both TCP and UDP sides. 98 | To specify an IPv6 address, use the following format: `[::1]:1234` with 99 | the command line options. Resolving AAAA record is also supported. Please run the program 100 | with `-h` to see detailed options on how to control the IPv6 behavior. 101 | 102 | [Back to TOC](#table-of-contents) 103 | 104 | ## 1. Enable Kernel IP forwarding 105 | 106 | Edit `/etc/sysctl.conf`, add `net.ipv4.ip_forward=1` and run `sudo sysctl -p /etc/sysctl.conf`. 107 | 108 |
109 | IPv6 specific config 110 | 111 | `net.ipv6.conf.all.forwarding=1` will need to be set as well. 112 |
113 | 114 | [Back to TOC](#table-of-contents) 115 | 116 | ## 2. Add required firewall rules 117 | 118 | 119 | ### Client 120 | 121 | Client simply need SNAT enabled on the physical interface to translate Phantun's address into 122 | one that can be used on the physical network. This can be done simply with masquerade. 123 | 124 | Note: change `eth0` to whatever actual physical interface name is 125 | 126 | [Back to TOC](#table-of-contents) 127 | 128 | #### Using nftables 129 | 130 | ``` 131 | table inet nat { 132 | chain postrouting { 133 | type nat hook postrouting priority srcnat; policy accept; 134 | iifname tun0 oif eth0 masquerade 135 | } 136 | } 137 | ``` 138 | 139 | Note: The above rule uses `inet` as the table family type, so it is compatible with 140 | both IPv4 and IPv6 usage. 141 | 142 | [Back to TOC](#table-of-contents) 143 | 144 | #### Using iptables 145 | 146 | ``` 147 | iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE 148 | ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE 149 | ``` 150 | 151 | [Back to TOC](#table-of-contents) 152 | 153 | ### Server 154 | 155 | Server needs to DNAT the TCP listening port to Phantun's TUN interface address. 156 | 157 | Note: change `eth0` to whatever actual physical interface name is and `4567` to 158 | actual TCP port number used by Phantun server 159 | 160 | [Back to TOC](#table-of-contents) 161 | 162 | #### Using nftables 163 | 164 | ``` 165 | table inet nat { 166 | chain prerouting { 167 | type nat hook prerouting priority dstnat; policy accept; 168 | iif eth0 tcp dport 4567 dnat ip to 192.168.201.2 169 | iif eth0 tcp dport 4567 dnat ip6 to fcc9::2 170 | } 171 | } 172 | ``` 173 | 174 | [Back to TOC](#table-of-contents) 175 | 176 | #### Using iptables 177 | 178 | ``` 179 | iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination 192.168.201.2 180 | ip6tables -t nat -A PREROUTING -p tcp -i eth0 --dport 4567 -j DNAT --to-destination fcc9::2 181 | ``` 182 | 183 | [Back to TOC](#table-of-contents) 184 | 185 | ## 3. Run Phantun binaries as non-root (Optional) 186 | 187 | It is ill-advised to run network facing applications as root user. Phantun can be run fully 188 | as non-root user with the `cap_net_admin` capability. 189 | 190 | ``` 191 | sudo setcap cap_net_admin=+pe phantun_server 192 | sudo setcap cap_net_admin=+pe phantun_client 193 | ``` 194 | 195 | 196 | [Back to TOC](#table-of-contents) 197 | 198 | ## 4. Start Phantun daemon 199 | 200 | **Note:** Run Phantun executable with `-h` option to see full detailed options. 201 | 202 | [Back to TOC](#table-of-contents) 203 | 204 | ### Server 205 | 206 | Note: `4567` is the TCP port Phantun should listen on and must corresponds to the DNAT 207 | rule specified above. `127.0.0.1:1234` is the UDP Server to connect to for new connections. 208 | 209 | ``` 210 | RUST_LOG=info /usr/local/bin/phantun_server --local 4567 --remote 127.0.0.1:1234 211 | ``` 212 | 213 | Or use host name with `--remote`: 214 | 215 | ``` 216 | RUST_LOG=info /usr/local/bin/phantun_server --local 4567 --remote example.com:1234 217 | ``` 218 | 219 | Note: Server by default assigns both IPv4 and IPv6 private address to the Tun interface. 220 | If you do not wish to use IPv6, you can simply skip creating the IPv6 DNAT rule above and 221 | the presence of IPv6 address on the Tun interface should have no side effect to the server. 222 | 223 | [Back to TOC](#table-of-contents) 224 | 225 | ### Client 226 | 227 | Note: `127.0.0.1:1234` is the UDP address and port Phantun should listen on. `10.0.0.1:4567` is 228 | the Phantun Server to connect. 229 | 230 | ``` 231 | RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote 10.0.0.1:4567 232 | ``` 233 | 234 | Or use host name with `--remote`: 235 | 236 | ``` 237 | RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote example.com:4567 238 | ``` 239 | 240 |
241 | IPv6 specific config 242 | 243 | ``` 244 | RUST_LOG=info /usr/local/bin/phantun_client --local 127.0.0.1:1234 --remote [fdxx::1234]:4567 245 | ``` 246 | 247 | Domain name with AAAA record is also supported. 248 |
249 | 250 | [Back to TOC](#table-of-contents) 251 | 252 | # MTU overhead 253 | 254 | Phantun aims to keep tunneling overhead to the minimum. The overhead compared to a plain UDP packet 255 | is the following (using IPv4 below as an example): 256 | 257 | **Standard UDP packet:** `20 byte IP header + 8 byte UDP header = 28 bytes` 258 | 259 | **Obfuscated packet:** `20 byte IP header + 20 byte TCP header = 40 bytes` 260 | 261 | 262 | Note that Phantun does not add any additional header other than IP and TCP headers in order to pass through 263 | stateful packet inspection! 264 | 265 | Phantun's additional overhead: `12 bytes`. In other words, when using Phantun, the usable payload for 266 | UDP packet is reduced by 12 bytes. This is the minimum overhead possible when doing such kind 267 | of obfuscation. 268 | 269 | ![Packet header diagram](images/packet-headers.png) 270 | 271 | [Back to TOC](#table-of-contents) 272 | 273 | ## MTU calculation for WireGuard 274 | 275 | For people who use Phantun to tunnel [WireGuard®](https://www.wireguard.com) UDP packets, here are some guidelines on figuring 276 | out the correct MTU to use for your WireGuard interface. 277 | 278 | ``` 279 | WireGuard MTU = Interface MTU - IPv4 header (20 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes) 280 | ``` 281 | 282 | or 283 | 284 | ``` 285 | WireGuard MTU = Interface MTU - IPv6 header (40 bytes) - TCP header (20 bytes) - WireGuard overhead (32 bytes) 286 | ``` 287 | 288 | For example, for a Ethernet interface with 1500 bytes MTU, the WireGuard interface MTU should be set as: 289 | 290 | IPv4: `1500 - 20 - 20 - 32 = 1428 bytes` 291 | IPv6: `1500 - 40 - 20 - 32 = 1408 bytes` 292 | 293 | The resulted Phantun TCP data packet will be 1500 bytes which does not exceed the 294 | interface MTU of 1500. Please note it is strongly recommended to use the same interface 295 | MTU for both ends of a WireGuard tunnel, or unexpected packet loss may occur and these issues are 296 | generally very hard to troubleshoot. 297 | 298 | [Back to TOC](#table-of-contents) 299 | 300 | # Version compatibility 301 | 302 | While the TCP stack is fairly stable, the general expectation is that you should run same minor versions 303 | of Server/Client of Phantun on both ends to ensure maximum compatibility. 304 | 305 | [Back to TOC](#table-of-contents) 306 | 307 | # Documentations 308 | 309 | For users who wish to use `fake-tcp` library inside their own project, refer to the documentations for the library at: 310 | [https://docs.rs/fake-tcp](https://docs.rs/fake-tcp). 311 | 312 | [Back to TOC](#table-of-contents) 313 | 314 | # Performance 315 | 316 | Performance was tested on 2 AWS `t4g.xlarge` instances with 4 vCPUs and 5 Gb/s NIC over LAN. `nftables` was used to redirect 317 | UDP stream of `iperf3` to go through the Phantun/udp2raw tunnel between two test instances and MTU has been tuned to avoid fragmentation. 318 | 319 | Phantun `v0.3.2` and `udp2raw_arm_asm_aes` `20200818.0` was used. These were the latest release of both projects as of Apr 2022. 320 | 321 | Test command: `iperf3 -c -p -R -u -l 1400 -b 1000m -t 30 -P 5` 322 | 323 | | Mode | Send Speed | Receive Speed | Overall CPU Usage | 324 | |---------------------------------------------------------------------------------|----------------|----------------|-----------------------------------------------------| 325 | | Direct (1 stream) | 3.00 Gbits/sec | 2.37 Gbits/sec | 25% (1 core at 100%) | 326 | | Phantun (1 stream) | 1.30 Gbits/sec | 1.20 Gbits/sec | 60% (1 core at 100%, 3 cores at 50%) | 327 | | udp2raw (`cipher-mode=none` `auth-mode=none` `disable-anti-replay`) (1 stream) | 1.30 Gbits/sec | 715 Mbits/sec | 40% (1 core at 100%, 1 core at 50%, 2 cores idling) | 328 | | Direct connection (5 streams) | 5.00 Gbits/sec | 3.64 Gbits/sec | 25% (1 core at 100%) | 329 | | Phantun (5 streams) | 5.00 Gbits/sec | 2.38 Gbits/sec | 95% (all cores utilized) | 330 | | udp2raw (`cipher-mode=none` `auth-mode=none` `disable-anti-replay`) (5 streams) | 5.00 Gbits/sec | 770 Mbits/sec | 50% (2 cores at 100%) | 331 | 332 | Writeup on some of the techniques used in Phantun to achieve this performance result: [Writing Highly Efficient UDP Server in Rust](https://idndx.com/writing-highly-efficient-udp-server-in-rust/). 333 | 334 | [Back to TOC](#table-of-contents) 335 | 336 | # Future plans 337 | 338 | * Load balancing a single UDP stream into multiple TCP streams 339 | * Integration tests 340 | * Auto insertion/removal of required firewall rules 341 | 342 | [Back to TOC](#table-of-contents) 343 | 344 | # Compariation to udp2raw 345 | [udp2raw](https://github.com/wangyu-/udp2raw-tunnel) is another popular project by [@wangyu-](https://github.com/wangyu-) 346 | that is very similar to what Phantun can do. In fact I took inspirations of Phantun from udp2raw. The biggest reason for 347 | developing Phantun is because of lack of performance when running udp2raw (especially on multi-core systems such as Raspberry Pi). 348 | However, the goal is never to be as feature complete as udp2raw and only support the most common use cases. Most notably, UDP over ICMP 349 | and UDP over UDP mode are not supported and there is no anti-replay nor encryption support. The benefit of this is much better 350 | performance overall and less MTU overhead because lack of additional headers inside the TCP payload. 351 | 352 | Here is a quick overview of comparison between those two to help you choose: 353 | 354 | | | Phantun | udp2raw | 355 | |--------------------------------------------------|:-------------:|:-----------------:| 356 | | UDP over FakeTCP obfuscation | ✅ | ✅ | 357 | | UDP over ICMP obfuscation | ❌ | ✅ | 358 | | UDP over UDP obfuscation | ❌ | ✅ | 359 | | Multi-threaded | ✅ | ❌ | 360 | | Throughput | Better | Good | 361 | | Layer 3 mode | TUN interface | Raw sockets + BPF | 362 | | Tunneling MTU overhead | 12 bytes | 44 bytes | 363 | | Seprate TCP connections for each UDP connection | Client/Server | Server only | 364 | | Anti-replay, encryption | ❌ | ✅ | 365 | | IPv6 | ✅ | ✅ | 366 | 367 | [Back to TOC](#table-of-contents) 368 | 369 | # License 370 | 371 | Copyright 2021-2024 Datong Sun (dndx@idndx.com) 372 | 373 | Licensed under the Apache License, Version 2.0 or the MIT license 375 | , at your 376 | option. Files in the project may not be 377 | copied, modified, or distributed except according to those terms. 378 | 379 | [Back to TOC](#table-of-contents) 380 | 381 | -------------------------------------------------------------------------------- /docker/.dockerignore: -------------------------------------------------------------------------------- 1 | README.md 2 | docker-compose.yml 3 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Dockerfile for phantun 3 | # 4 | 5 | # 6 | # Build stage 7 | # 8 | FROM rust:latest AS builder 9 | 10 | COPY . /phantun 11 | 12 | RUN cd phantun \ 13 | && cargo build --release \ 14 | && strip target/release/server target/release/client \ 15 | && install target/release/server /usr/local/bin/phantun-server \ 16 | && install target/release/client /usr/local/bin/phantun-client \ 17 | && cd - \ 18 | && rm -r phantun 19 | 20 | # 21 | # Runtime stage 22 | # 23 | FROM debian:latest 24 | 25 | COPY --from=builder /usr/local/bin/phantun-server /usr/local/bin/ 26 | COPY --from=builder /usr/local/bin/phantun-client /usr/local/bin/ 27 | COPY docker/phantun.sh /usr/local/bin/ 28 | 29 | ENV USE_IPTABLES_NFT_BACKEND=0 30 | ENV RUST_LOG=INFO 31 | 32 | ENTRYPOINT ["phantun.sh"] 33 | CMD ["phantun-server", "--help"] 34 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # phantun (docker) 2 | 3 | ## Build 4 | 5 | ```sh 6 | docker build -t phantun -f docker/Dockerfile . 7 | ``` 8 | 9 | ## Usage 10 | 11 | It is recommended to use docker-compose, see [docker-compose.yml](docker-compose.yml) for details. 12 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | phantun-server: 5 | image: phantun 6 | container_name: phantun-server 7 | restart: unless-stopped 8 | network_mode: host 9 | privileged: true 10 | environment: 11 | USE_IPTABLES_NFT_BACKEND: 0 12 | RUST_LOG: INFO 13 | command: > 14 | phantun-server --local 1985 --remote 127.0.0.1:1984 --ipv4-only 15 | 16 | phantun-client: 17 | image: phantun 18 | container_name: phantun-client 19 | restart: unless-stopped 20 | network_mode: host 21 | privileged: true 22 | environment: 23 | USE_IPTABLES_NFT_BACKEND: 0 24 | RUST_LOG: INFO 25 | command: > 26 | phantun-client --local 127.0.0.1:1984 --remote 11.22.33.44:1985 --ipv4-only 27 | -------------------------------------------------------------------------------- /docker/phantun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # alias ​​settings must be global, and must be defined before the function being called with the alias 4 | if [ "$USE_IPTABLES_NFT_BACKEND" = 1 ]; then 5 | alias iptables=iptables-nft 6 | alias iptables-save=iptables-nft-save 7 | alias ip6tables=ip6tables-nft 8 | alias ip6tables-save=ip6tables-nft-save 9 | fi 10 | 11 | info() { 12 | local green='\e[0;32m' 13 | local clear='\e[0m' 14 | local time=$(date '+%Y-%m-%d %T') 15 | printf "${green}[${time}] [INFO]: ${clear}%s\n" "$*" 16 | } 17 | 18 | warn() { 19 | local yellow='\e[1;33m' 20 | local clear='\e[0m' 21 | local time=$(date '+%Y-%m-%d %T') 22 | printf "${yellow}[${time}] [WARN]: ${clear}%s\n" "$*" >&2 23 | } 24 | 25 | error() { 26 | local red='\e[0;31m' 27 | local clear='\e[0m' 28 | local time=$(date '+%Y-%m-%d %T') 29 | printf "${red}[${time}] [ERROR]: ${clear}%s\n" "$*" >&2 30 | } 31 | 32 | _get_default_iface() { 33 | ip -4 route show default | awk -F 'dev' '{print $2}' | awk '{print $1}' 34 | } 35 | 36 | _get_default6_iface() { 37 | ip -6 route show default | awk -F 'dev' '{print $2}' | awk '{print $1}' 38 | } 39 | 40 | _get_addr_by_iface() { 41 | ip -4 addr show dev "$1" | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | head -1 42 | } 43 | 44 | _get_addr6_by_iface() { 45 | ip -6 addr show dev "$1" | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | head -1 46 | } 47 | 48 | _check_rule_by_comment() { 49 | iptables-save | grep -q "$1" 50 | } 51 | 52 | _check_rule6_by_comment() { 53 | ip6tables-save | grep -q "$1" 54 | } 55 | 56 | _is_server_mode() { 57 | [ "$1" = "phantun-server" ] 58 | } 59 | 60 | _is_ipv4_only() { 61 | case "$@" in 62 | *-4*|*--ipv4-only*) 63 | return 0 64 | ;; 65 | *\ -4*|*\ --ipv4-only*) 66 | return 0 67 | ;; 68 | esac 69 | return 1 70 | } 71 | 72 | _get_tun_from_args() { 73 | local tun=$(echo "$@" | awk -F '--tun' '{print $2}' | awk '{print $1}') 74 | echo ${tun:=tun0} 75 | } 76 | 77 | _get_peer_from_args() { 78 | local peer=$(echo "$@" | awk -F '--tun-peer' '{print $2}' | awk '{print $1}') 79 | _is_server_mode "$1" && echo ${peer:=192.168.201.2} || echo ${peer:=192.168.200.2} 80 | } 81 | 82 | _get_peer6_from_args() { 83 | local peer=$(echo "$@" | awk -F '--tun-peer6' '{print $2}' | awk '{print $1}') 84 | _is_server_mode "$1" && echo ${peer:=fcc9::2} || echo ${peer:=fcc8::2} 85 | } 86 | 87 | _get_port_from_args() { 88 | local value=$(echo "$@" | awk -F '-l|--local' '{print $2}' | awk '{print $1}') 89 | _is_server_mode "$1" && echo $value || echo $value | awk -F ':' '{print $2}' 90 | } 91 | 92 | _iptables() { 93 | iptables -w 10 "$@" 94 | } 95 | 96 | _ip6tables() { 97 | ip6tables -w 10 "$@" 98 | } 99 | 100 | apply_sysctl() { 101 | info "apply sysctl: $(sysctl -w net.ipv4.ip_forward=1)" 102 | ! _is_ipv4_only "$@" || return 103 | info "apply sysctl: $(sysctl -w net.ipv6.conf.all.forwarding=1)" 104 | } 105 | 106 | apply_iptables() { 107 | local interface=$(_get_default_iface) 108 | local address=$(_get_addr_by_iface "${interface}") 109 | local tun=$(_get_tun_from_args "$@") 110 | local peer=$(_get_peer_from_args "$@") 111 | local port=$(_get_port_from_args "$@") 112 | local comment="phantun_${tun}_${port}" 113 | 114 | if _check_rule_by_comment "${comment}"; then 115 | warn "iptables rules already exist, maybe needs to check." 116 | else 117 | _iptables -A FORWARD -i $tun -j ACCEPT -m comment --comment "${comment}" || error "iptables filter rule add failed." 118 | _iptables -A FORWARD -o $tun -j ACCEPT -m comment --comment "${comment}" || error "iptables filter rule add failed." 119 | if _is_server_mode "$1"; then 120 | info "iptables DNAT rule added: [${comment}]: ${interface} -> ${tun}, ${address} -> ${peer}" 121 | _iptables -t nat -A PREROUTING -p tcp -i $interface --dport $port -j DNAT --to-destination $peer \ 122 | -m comment --comment "${comment}" || error "iptables DNAT rule add failed." 123 | else 124 | info "iptables SNAT rule added: [${comment}]: ${tun} -> ${interface}, ${peer} -> ${address}" 125 | _iptables -t nat -A POSTROUTING -s $peer -o $interface -j SNAT --to-source $address \ 126 | -m comment --comment "${comment}" || error "iptables SNAT rule add failed." 127 | fi 128 | fi 129 | } 130 | 131 | apply_ip6tables() { 132 | ! _is_ipv4_only "$@" || return 133 | 134 | local interface=$(_get_default6_iface) 135 | local address=$(_get_addr6_by_iface "${interface}") 136 | local tun=$(_get_tun_from_args "$@") 137 | local peer=$(_get_peer6_from_args "$@") 138 | local port=$(_get_port_from_args "$@") 139 | local comment="phantun_${tun}_${port}" 140 | 141 | if _check_rule6_by_comment "${comment}"; then 142 | warn "ip6tables rules already exist, maybe needs to check." 143 | else 144 | _ip6tables -A FORWARD -i $tun -j ACCEPT -m comment --comment "${comment}" || error "ip6tables filter rule add failed." 145 | _ip6tables -A FORWARD -o $tun -j ACCEPT -m comment --comment "${comment}" || error "ip6tables filter rule add failed." 146 | if _is_server_mode "$1"; then 147 | info "ip6tables DNAT rule added: [${comment}]: ${interface} -> ${tun}, ${address} -> ${peer}" 148 | _ip6tables -t nat -A PREROUTING -p tcp -i $interface --dport $port -j DNAT --to-destination $peer \ 149 | -m comment --comment "${comment}" || error "ip6tables DNAT rule add failed." 150 | else 151 | info "ip6tables SNAT rule added: [${comment}]: ${tun} -> ${interface}, ${peer} -> ${address}" 152 | _ip6tables -t nat -A POSTROUTING -s $peer -o $interface -j SNAT --to-source $address \ 153 | -m comment --comment "${comment}" || error "ip6tables SNAT rule add failed." 154 | fi 155 | fi 156 | } 157 | 158 | stop_process() { 159 | kill $(pidof phantun-server phantun-client) 160 | info "terminate phantun process." 161 | } 162 | 163 | revoke_iptables() { 164 | local tun=$(_get_tun_from_args "$@") 165 | local port=$(_get_port_from_args "$@") 166 | local comment="phantun_${tun}_${port}" 167 | 168 | iptables-save -t filter | grep "${comment}" | while read rule; do 169 | _iptables -t filter ${rule/-A/-D} || error "iptables filter rule remove failed." 170 | done 171 | iptables-save -t nat | grep "${comment}" | while read rule; do 172 | _iptables -t nat ${rule/-A/-D} || error "iptables nat rule remove failed." 173 | done 174 | info "iptables rule: [${comment}] removed." 175 | } 176 | 177 | revoke_ip6tables() { 178 | ! _is_ipv4_only "$@" || return 179 | 180 | local tun=$(_get_tun_from_args "$@") 181 | local port=$(_get_port_from_args "$@") 182 | local comment="phantun_${tun}_${port}" 183 | 184 | ip6tables-save -t filter | grep "${comment}" | while read rule; do 185 | _ip6tables -t filter ${rule/-A/-D} || error "ip6tables filter rule remove failed." 186 | done 187 | ip6tables-save -t nat | grep "${comment}" | while read rule; do 188 | _ip6tables -t nat ${rule/-A/-D} || error "ip6tables nat rule remove failed." 189 | done 190 | info "ip6tables rule: [${comment}] removed." 191 | } 192 | 193 | graceful_stop() { 194 | warn "caught SIGTERM or SIGINT signal, graceful stopping..." 195 | stop_process 196 | revoke_iptables "$@" 197 | revoke_ip6tables "$@" 198 | } 199 | 200 | start_phantun() { 201 | trap 'graceful_stop "$@"' SIGTERM SIGINT 202 | apply_sysctl "$@" 203 | apply_iptables "$@" 204 | apply_ip6tables "$@" 205 | "$@" & 206 | wait 207 | } 208 | 209 | start_phantun "$@" 210 | -------------------------------------------------------------------------------- /fake-tcp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fake-tcp" 3 | version = "0.6.0" 4 | edition = "2021" 5 | authors = ["Datong Sun "] 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/dndx/phantun" 8 | readme = "README.md" 9 | description = """ 10 | A TUN interface based, user space, asynchronous and high performance TCP stack that allows 11 | packet oriented tunneling with minimum overhead. 12 | """ 13 | 14 | [features] 15 | benchmark = [] 16 | 17 | [dependencies] 18 | bytes = "1" 19 | pnet = "0" 20 | tokio = { version = "1", features = ["full"] } 21 | rand = { version = "0", features = ["small_rng"] } 22 | log = "0" 23 | internet-checksum = "0" 24 | tokio-tun = "0" 25 | flume = "0" 26 | -------------------------------------------------------------------------------- /fake-tcp/LICENSE-APACHE: -------------------------------------------------------------------------------- 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 2021-2024 Datong Sun (dndx@idndx.com) 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 | -------------------------------------------------------------------------------- /fake-tcp/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2024 Datong Sun (dndx@idndx.com) 4 | 5 | Permission is hereby granted, free of charge, to any 6 | person obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the 8 | Software without restriction, including without 9 | limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software 12 | is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions 17 | of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /fake-tcp/README.md: -------------------------------------------------------------------------------- 1 | # fake-tcp 2 | 3 | A TUN interface based, user space, asynchronous and high performance TCP stack that allows 4 | packet oriented tunneling with minimum overhead. 5 | 6 | ## License 7 | 8 | Copyright 2021 Datong Sun 9 | 10 | Licensed under the Apache License, Version 2.0 or the MIT license 12 | , at your 13 | option. Files in the project may not be 14 | copied, modified, or distributed except according to those terms. 15 | -------------------------------------------------------------------------------- /fake-tcp/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A minimum, userspace TCP based datagram stack 2 | //! 3 | //! # Overview 4 | //! 5 | //! `fake-tcp` is a reusable library that implements a minimum TCP stack in 6 | //! user space using the Tun interface. It allows programs to send datagrams 7 | //! as if they are part of a TCP connection. `fake-tcp` has been tested to 8 | //! be able to pass through a variety of NAT and stateful firewalls while 9 | //! fully preserves certain desirable behavior such as out of order delivery 10 | //! and no congestion/flow controls. 11 | //! 12 | //! # Core Concepts 13 | //! 14 | //! The core of the `fake-tcp` crate compose of two structures. [`Stack`] and 15 | //! [`Socket`]. 16 | //! 17 | //! ## [`Stack`] 18 | //! 19 | //! [`Stack`] represents a virtual TCP stack that operates at 20 | //! Layer 3. It is responsible for: 21 | //! 22 | //! * TCP active and passive open and handshake 23 | //! * `RST` handling 24 | //! * Interact with the Tun interface at Layer 3 25 | //! * Distribute incoming datagrams to corresponding [`Socket`] 26 | //! 27 | //! ## [`Socket`] 28 | //! 29 | //! [`Socket`] represents a TCP connection. It registers the identifying 30 | //! tuple `(src_ip, src_port, dest_ip, dest_port)` inside the [`Stack`] so 31 | //! so that incoming packets can be distributed to the right [`Socket`] with 32 | //! using a channel. It is also what the client should use for 33 | //! sending/receiving datagrams. 34 | //! 35 | //! # Examples 36 | //! 37 | //! Please see [`client.rs`](https://github.com/dndx/phantun/blob/main/phantun/src/bin/client.rs) 38 | //! and [`server.rs`](https://github.com/dndx/phantun/blob/main/phantun/src/bin/server.rs) files 39 | //! from the `phantun` crate for how to use this library in client/server mode, respectively. 40 | 41 | #![cfg_attr(feature = "benchmark", feature(test))] 42 | 43 | pub mod packet; 44 | 45 | use bytes::{Bytes, BytesMut}; 46 | use log::{error, info, trace, warn}; 47 | use packet::*; 48 | use pnet::packet::{tcp, Packet}; 49 | use rand::prelude::*; 50 | use std::collections::{HashMap, HashSet}; 51 | use std::fmt; 52 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 53 | use std::sync::{ 54 | atomic::{AtomicU32, Ordering}, 55 | Arc, RwLock, 56 | }; 57 | use tokio::sync::broadcast; 58 | use tokio::sync::mpsc; 59 | use tokio::time; 60 | use tokio_tun::Tun; 61 | 62 | const TIMEOUT: time::Duration = time::Duration::from_secs(1); 63 | const RETRIES: usize = 6; 64 | const MPMC_BUFFER_LEN: usize = 512; 65 | const MPSC_BUFFER_LEN: usize = 128; 66 | const MAX_UNACKED_LEN: u32 = 128 * 1024 * 1024; // 128MB 67 | 68 | #[derive(Hash, Eq, PartialEq, Clone, Debug)] 69 | struct AddrTuple { 70 | local_addr: SocketAddr, 71 | remote_addr: SocketAddr, 72 | } 73 | 74 | impl AddrTuple { 75 | fn new(local_addr: SocketAddr, remote_addr: SocketAddr) -> AddrTuple { 76 | AddrTuple { 77 | local_addr, 78 | remote_addr, 79 | } 80 | } 81 | } 82 | 83 | struct Shared { 84 | tuples: RwLock>>, 85 | listening: RwLock>, 86 | tun: Vec>, 87 | ready: mpsc::Sender, 88 | tuples_purge: broadcast::Sender, 89 | } 90 | 91 | pub struct Stack { 92 | shared: Arc, 93 | local_ip: Ipv4Addr, 94 | local_ip6: Option, 95 | ready: mpsc::Receiver, 96 | } 97 | 98 | pub enum State { 99 | Idle, 100 | SynSent, 101 | SynReceived, 102 | Established, 103 | } 104 | 105 | pub struct Socket { 106 | shared: Arc, 107 | tun: Arc, 108 | incoming: flume::Receiver, 109 | local_addr: SocketAddr, 110 | remote_addr: SocketAddr, 111 | seq: AtomicU32, 112 | ack: AtomicU32, 113 | last_ack: AtomicU32, 114 | state: State, 115 | } 116 | 117 | /// A socket that represents a unique TCP connection between a server and client. 118 | /// 119 | /// The `Socket` object itself satisfies `Sync` and `Send`, which means it can 120 | /// be safely called within an async future. 121 | /// 122 | /// To close a TCP connection that is no longer needed, simply drop this object 123 | /// out of scope. 124 | impl Socket { 125 | fn new( 126 | shared: Arc, 127 | tun: Arc, 128 | local_addr: SocketAddr, 129 | remote_addr: SocketAddr, 130 | ack: Option, 131 | state: State, 132 | ) -> (Socket, flume::Sender) { 133 | let (incoming_tx, incoming_rx) = flume::bounded(MPMC_BUFFER_LEN); 134 | 135 | ( 136 | Socket { 137 | shared, 138 | tun, 139 | incoming: incoming_rx, 140 | local_addr, 141 | remote_addr, 142 | seq: AtomicU32::new(0), 143 | ack: AtomicU32::new(ack.unwrap_or(0)), 144 | last_ack: AtomicU32::new(ack.unwrap_or(0)), 145 | state, 146 | }, 147 | incoming_tx, 148 | ) 149 | } 150 | 151 | fn build_tcp_packet(&self, flags: u8, payload: Option<&[u8]>) -> Bytes { 152 | let ack = self.ack.load(Ordering::Relaxed); 153 | self.last_ack.store(ack, Ordering::Relaxed); 154 | 155 | build_tcp_packet( 156 | self.local_addr, 157 | self.remote_addr, 158 | self.seq.load(Ordering::Relaxed), 159 | ack, 160 | flags, 161 | payload, 162 | ) 163 | } 164 | 165 | /// Sends a datagram to the other end. 166 | /// 167 | /// This method takes `&self`, and it can be called safely by multiple threads 168 | /// at the same time. 169 | /// 170 | /// A return of `None` means the Tun socket returned an error 171 | /// and this socket must be closed. 172 | pub async fn send(&self, payload: &[u8]) -> Option<()> { 173 | match self.state { 174 | State::Established => { 175 | let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, Some(payload)); 176 | self.seq.fetch_add(payload.len() as u32, Ordering::Relaxed); 177 | self.tun.send(&buf).await.ok().and(Some(())) 178 | } 179 | _ => unreachable!(), 180 | } 181 | } 182 | 183 | /// Attempt to receive a datagram from the other end. 184 | /// 185 | /// This method takes `&self`, and it can be called safely by multiple threads 186 | /// at the same time. 187 | /// 188 | /// A return of `None` means the TCP connection is broken 189 | /// and this socket must be closed. 190 | pub async fn recv(&self, buf: &mut [u8]) -> Option { 191 | match self.state { 192 | State::Established => { 193 | self.incoming.recv_async().await.ok().and_then(|raw_buf| { 194 | let (_v4_packet, tcp_packet) = parse_ip_packet(&raw_buf).unwrap(); 195 | 196 | if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 { 197 | info!("Connection {} reset by peer", self); 198 | return None; 199 | } 200 | 201 | let payload = tcp_packet.payload(); 202 | 203 | let new_ack = tcp_packet.get_sequence().wrapping_add(payload.len() as u32); 204 | let last_ask = self.last_ack.load(Ordering::Relaxed); 205 | self.ack.store(new_ack, Ordering::Relaxed); 206 | 207 | if new_ack.overflowing_sub(last_ask).0 > MAX_UNACKED_LEN { 208 | let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, None); 209 | if let Err(e) = self.tun.try_send(&buf) { 210 | // This should not really happen as we have not sent anything for 211 | // quite some time... 212 | info!("Connection {} unable to send idling ACK back: {}", self, e) 213 | } 214 | } 215 | 216 | buf[..payload.len()].copy_from_slice(payload); 217 | 218 | Some(payload.len()) 219 | }) 220 | } 221 | _ => unreachable!(), 222 | } 223 | } 224 | 225 | async fn accept(mut self) { 226 | for _ in 0..RETRIES { 227 | match self.state { 228 | State::Idle => { 229 | let buf = self.build_tcp_packet(tcp::TcpFlags::SYN | tcp::TcpFlags::ACK, None); 230 | // ACK set by constructor 231 | self.tun.send(&buf).await.unwrap(); 232 | self.state = State::SynReceived; 233 | info!("Sent SYN + ACK to client"); 234 | } 235 | State::SynReceived => { 236 | let res = time::timeout(TIMEOUT, self.incoming.recv_async()).await; 237 | if let Ok(buf) = res { 238 | let buf = buf.unwrap(); 239 | let (_v4_packet, tcp_packet) = parse_ip_packet(&buf).unwrap(); 240 | 241 | if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 { 242 | return; 243 | } 244 | 245 | if tcp_packet.get_flags() == tcp::TcpFlags::ACK 246 | && tcp_packet.get_acknowledgement() 247 | == self.seq.load(Ordering::Relaxed) + 1 248 | { 249 | // found our ACK 250 | self.seq.fetch_add(1, Ordering::Relaxed); 251 | self.state = State::Established; 252 | 253 | info!("Connection from {:?} established", self.remote_addr); 254 | let ready = self.shared.ready.clone(); 255 | if let Err(e) = ready.send(self).await { 256 | error!("Unable to send accepted socket to ready queue: {}", e); 257 | } 258 | return; 259 | } 260 | } else { 261 | info!("Waiting for client ACK timed out"); 262 | self.state = State::Idle; 263 | } 264 | } 265 | _ => unreachable!(), 266 | } 267 | } 268 | } 269 | 270 | async fn connect(&mut self) -> Option<()> { 271 | for _ in 0..RETRIES { 272 | match self.state { 273 | State::Idle => { 274 | let buf = self.build_tcp_packet(tcp::TcpFlags::SYN, None); 275 | self.tun.send(&buf).await.unwrap(); 276 | self.state = State::SynSent; 277 | info!("Sent SYN to server"); 278 | } 279 | State::SynSent => { 280 | match time::timeout(TIMEOUT, self.incoming.recv_async()).await { 281 | Ok(buf) => { 282 | let buf = buf.unwrap(); 283 | let (_v4_packet, tcp_packet) = parse_ip_packet(&buf).unwrap(); 284 | 285 | if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 { 286 | return None; 287 | } 288 | 289 | if tcp_packet.get_flags() == tcp::TcpFlags::SYN | tcp::TcpFlags::ACK 290 | && tcp_packet.get_acknowledgement() 291 | == self.seq.load(Ordering::Relaxed) + 1 292 | { 293 | // found our SYN + ACK 294 | self.seq.fetch_add(1, Ordering::Relaxed); 295 | self.ack 296 | .store(tcp_packet.get_sequence() + 1, Ordering::Relaxed); 297 | 298 | // send ACK to finish handshake 299 | let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, None); 300 | self.tun.send(&buf).await.unwrap(); 301 | 302 | self.state = State::Established; 303 | 304 | info!("Connection to {:?} established", self.remote_addr); 305 | return Some(()); 306 | } 307 | } 308 | Err(_) => { 309 | info!("Waiting for SYN + ACK timed out"); 310 | self.state = State::Idle; 311 | } 312 | } 313 | } 314 | _ => unreachable!(), 315 | } 316 | } 317 | 318 | None 319 | } 320 | } 321 | 322 | impl Drop for Socket { 323 | /// Drop the socket and close the TCP connection 324 | fn drop(&mut self) { 325 | let tuple = AddrTuple::new(self.local_addr, self.remote_addr); 326 | // dissociates ourself from the dispatch map 327 | assert!(self.shared.tuples.write().unwrap().remove(&tuple).is_some()); 328 | // purge cache 329 | self.shared.tuples_purge.send(tuple).unwrap(); 330 | 331 | let buf = build_tcp_packet( 332 | self.local_addr, 333 | self.remote_addr, 334 | self.seq.load(Ordering::Relaxed), 335 | 0, 336 | tcp::TcpFlags::RST, 337 | None, 338 | ); 339 | if let Err(e) = self.tun.try_send(&buf) { 340 | warn!("Unable to send RST to remote end: {}", e); 341 | } 342 | 343 | info!("Fake TCP connection to {} closed", self); 344 | } 345 | } 346 | 347 | impl fmt::Display for Socket { 348 | /// User-friendly string representation of the socket 349 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 350 | write!( 351 | f, 352 | "(Fake TCP connection from {} to {})", 353 | self.local_addr, self.remote_addr 354 | ) 355 | } 356 | } 357 | 358 | /// A userspace TCP state machine 359 | impl Stack { 360 | /// Create a new stack, `tun` is an array of [`Tun`](tokio_tun::Tun). 361 | /// When more than one [`Tun`](tokio_tun::Tun) object is passed in, same amount 362 | /// of reader will be spawned later. This allows user to utilize the performance 363 | /// benefit of Multiqueue Tun support on machines with SMP. 364 | pub fn new(tun: Vec, local_ip: Ipv4Addr, local_ip6: Option) -> Stack { 365 | let tun: Vec> = tun.into_iter().map(Arc::new).collect(); 366 | let (ready_tx, ready_rx) = mpsc::channel(MPSC_BUFFER_LEN); 367 | let (tuples_purge_tx, _tuples_purge_rx) = broadcast::channel(16); 368 | let shared = Arc::new(Shared { 369 | tuples: RwLock::new(HashMap::new()), 370 | tun: tun.clone(), 371 | listening: RwLock::new(HashSet::new()), 372 | ready: ready_tx, 373 | tuples_purge: tuples_purge_tx.clone(), 374 | }); 375 | 376 | for t in tun { 377 | tokio::spawn(Stack::reader_task( 378 | t, 379 | shared.clone(), 380 | tuples_purge_tx.subscribe(), 381 | )); 382 | } 383 | 384 | Stack { 385 | shared, 386 | local_ip, 387 | local_ip6, 388 | ready: ready_rx, 389 | } 390 | } 391 | 392 | /// Listens for incoming connections on the given `port`. 393 | pub fn listen(&mut self, port: u16) { 394 | assert!(self.shared.listening.write().unwrap().insert(port)); 395 | } 396 | 397 | /// Accepts an incoming connection. 398 | pub async fn accept(&mut self) -> Socket { 399 | self.ready.recv().await.unwrap() 400 | } 401 | 402 | /// Connects to the remote end. `None` returned means 403 | /// the connection attempt failed. 404 | pub async fn connect(&mut self, addr: SocketAddr) -> Option { 405 | let mut rng = SmallRng::from_os_rng(); 406 | for local_port in rng.random_range(32768..=60999)..=60999 { 407 | let local_addr = SocketAddr::new( 408 | if addr.is_ipv4() { 409 | IpAddr::V4(self.local_ip) 410 | } else { 411 | IpAddr::V6(self.local_ip6.expect("IPv6 local address undefined")) 412 | }, 413 | local_port, 414 | ); 415 | let tuple = AddrTuple::new(local_addr, addr); 416 | let mut sock; 417 | 418 | { 419 | let mut tuples = self.shared.tuples.write().unwrap(); 420 | if tuples.contains_key(&tuple) { 421 | trace!( 422 | "Fake TCP connection to {}, local port number {} already in use, trying another one", 423 | addr, local_port 424 | ); 425 | continue; 426 | } 427 | 428 | let incoming; 429 | (sock, incoming) = Socket::new( 430 | self.shared.clone(), 431 | self.shared.tun.choose(&mut rng).unwrap().clone(), 432 | local_addr, 433 | addr, 434 | None, 435 | State::Idle, 436 | ); 437 | 438 | assert!(tuples.insert(tuple, incoming).is_none()); 439 | } 440 | 441 | return sock.connect().await.map(|_| sock); 442 | } 443 | 444 | error!( 445 | "Fake TCP connection to {} failed, emphemeral port number exhausted", 446 | addr 447 | ); 448 | None 449 | } 450 | 451 | async fn reader_task( 452 | tun: Arc, 453 | shared: Arc, 454 | mut tuples_purge: broadcast::Receiver, 455 | ) { 456 | let mut tuples: HashMap> = HashMap::new(); 457 | 458 | loop { 459 | let mut buf = BytesMut::zeroed(MAX_PACKET_LEN); 460 | 461 | tokio::select! { 462 | size = tun.recv(&mut buf) => { 463 | let size = size.unwrap(); 464 | buf.truncate(size); 465 | let buf = buf.freeze(); 466 | 467 | match parse_ip_packet(&buf) { 468 | Some((ip_packet, tcp_packet)) => { 469 | let local_addr = 470 | SocketAddr::new(ip_packet.get_destination(), tcp_packet.get_destination()); 471 | let remote_addr = SocketAddr::new(ip_packet.get_source(), tcp_packet.get_source()); 472 | 473 | let tuple = AddrTuple::new(local_addr, remote_addr); 474 | if let Some(c) = tuples.get(&tuple) { 475 | if c.send_async(buf).await.is_err() { 476 | trace!("Cache hit, but receiver already closed, dropping packet"); 477 | } 478 | 479 | continue; 480 | 481 | // If not Ok, receiver has been closed and just fall through to the slow 482 | // path below 483 | } else { 484 | trace!("Cache miss, checking the shared tuples table for connection"); 485 | let sender = { 486 | let tuples = shared.tuples.read().unwrap(); 487 | tuples.get(&tuple).cloned() 488 | }; 489 | 490 | if let Some(c) = sender { 491 | trace!("Storing connection information into local tuples"); 492 | tuples.insert(tuple, c.clone()); 493 | c.send_async(buf).await.unwrap(); 494 | continue; 495 | } 496 | } 497 | 498 | if tcp_packet.get_flags() == tcp::TcpFlags::SYN 499 | && shared 500 | .listening 501 | .read() 502 | .unwrap() 503 | .contains(&tcp_packet.get_destination()) 504 | { 505 | // SYN seen on listening socket 506 | if tcp_packet.get_sequence() == 0 { 507 | let (sock, incoming) = Socket::new( 508 | shared.clone(), 509 | tun.clone(), 510 | local_addr, 511 | remote_addr, 512 | Some(tcp_packet.get_sequence() + 1), 513 | State::Idle, 514 | ); 515 | assert!(shared 516 | .tuples 517 | .write() 518 | .unwrap() 519 | .insert(tuple, incoming) 520 | .is_none()); 521 | tokio::spawn(sock.accept()); 522 | } else { 523 | trace!("Bad TCP SYN packet from {}, sending RST", remote_addr); 524 | let buf = build_tcp_packet( 525 | local_addr, 526 | remote_addr, 527 | 0, 528 | tcp_packet.get_sequence() + tcp_packet.payload().len() as u32 + 1, // +1 because of SYN flag set 529 | tcp::TcpFlags::RST | tcp::TcpFlags::ACK, 530 | None, 531 | ); 532 | shared.tun[0].try_send(&buf).unwrap(); 533 | } 534 | } else if (tcp_packet.get_flags() & tcp::TcpFlags::RST) == 0 { 535 | info!("Unknown TCP packet from {}, sending RST", remote_addr); 536 | let buf = build_tcp_packet( 537 | local_addr, 538 | remote_addr, 539 | tcp_packet.get_acknowledgement(), 540 | tcp_packet.get_sequence() + tcp_packet.payload().len() as u32, 541 | tcp::TcpFlags::RST | tcp::TcpFlags::ACK, 542 | None, 543 | ); 544 | shared.tun[0].try_send(&buf).unwrap(); 545 | } 546 | } 547 | None => { 548 | continue; 549 | } 550 | } 551 | }, 552 | tuple = tuples_purge.recv() => { 553 | let tuple = tuple.unwrap(); 554 | tuples.remove(&tuple); 555 | trace!("Removed cached tuple: {:?}", tuple); 556 | } 557 | } 558 | } 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /fake-tcp/src/packet.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Bytes, BytesMut}; 2 | use internet_checksum::Checksum; 3 | use pnet::packet::Packet; 4 | use pnet::packet::{ip, ipv4, ipv6, tcp}; 5 | use std::convert::TryInto; 6 | use std::net::{IpAddr, SocketAddr}; 7 | 8 | const IPV4_HEADER_LEN: usize = 20; 9 | const IPV6_HEADER_LEN: usize = 40; 10 | const TCP_HEADER_LEN: usize = 20; 11 | pub const MAX_PACKET_LEN: usize = 1500; 12 | 13 | pub enum IPPacket<'p> { 14 | V4(ipv4::Ipv4Packet<'p>), 15 | V6(ipv6::Ipv6Packet<'p>), 16 | } 17 | 18 | impl IPPacket<'_> { 19 | pub fn get_source(&self) -> IpAddr { 20 | match self { 21 | IPPacket::V4(p) => IpAddr::V4(p.get_source()), 22 | IPPacket::V6(p) => IpAddr::V6(p.get_source()), 23 | } 24 | } 25 | 26 | pub fn get_destination(&self) -> IpAddr { 27 | match self { 28 | IPPacket::V4(p) => IpAddr::V4(p.get_destination()), 29 | IPPacket::V6(p) => IpAddr::V6(p.get_destination()), 30 | } 31 | } 32 | } 33 | 34 | pub fn build_tcp_packet( 35 | local_addr: SocketAddr, 36 | remote_addr: SocketAddr, 37 | seq: u32, 38 | ack: u32, 39 | flags: u8, 40 | payload: Option<&[u8]>, 41 | ) -> Bytes { 42 | let ip_header_len = match local_addr { 43 | SocketAddr::V4(_) => IPV4_HEADER_LEN, 44 | SocketAddr::V6(_) => IPV6_HEADER_LEN, 45 | }; 46 | let wscale = (flags & tcp::TcpFlags::SYN) != 0; 47 | let tcp_header_len = TCP_HEADER_LEN + if wscale { 4 } else { 0 }; // nop + wscale 48 | let tcp_total_len = tcp_header_len + payload.map_or(0, |payload| payload.len()); 49 | let total_len = ip_header_len + tcp_total_len; 50 | let mut buf = BytesMut::zeroed(total_len); 51 | 52 | let mut ip_buf = buf.split_to(ip_header_len); 53 | let mut tcp_buf = buf.split_to(tcp_total_len); 54 | assert_eq!(0, buf.len()); 55 | 56 | match (local_addr, remote_addr) { 57 | (SocketAddr::V4(local), SocketAddr::V4(remote)) => { 58 | let mut v4 = ipv4::MutableIpv4Packet::new(&mut ip_buf).unwrap(); 59 | v4.set_version(4); 60 | v4.set_header_length(IPV4_HEADER_LEN as u8 / 4); 61 | v4.set_next_level_protocol(ip::IpNextHeaderProtocols::Tcp); 62 | v4.set_ttl(64); 63 | v4.set_source(*local.ip()); 64 | v4.set_destination(*remote.ip()); 65 | v4.set_total_length(total_len.try_into().unwrap()); 66 | v4.set_flags(ipv4::Ipv4Flags::DontFragment); 67 | let mut cksm = Checksum::new(); 68 | cksm.add_bytes(v4.packet()); 69 | v4.set_checksum(u16::from_be_bytes(cksm.checksum())); 70 | } 71 | (SocketAddr::V6(local), SocketAddr::V6(remote)) => { 72 | let mut v6 = ipv6::MutableIpv6Packet::new(&mut ip_buf).unwrap(); 73 | v6.set_version(6); 74 | v6.set_payload_length(tcp_total_len.try_into().unwrap()); 75 | v6.set_next_header(ip::IpNextHeaderProtocols::Tcp); 76 | v6.set_hop_limit(64); 77 | v6.set_source(*local.ip()); 78 | v6.set_destination(*remote.ip()); 79 | } 80 | _ => unreachable!(), 81 | }; 82 | 83 | let mut tcp = tcp::MutableTcpPacket::new(&mut tcp_buf).unwrap(); 84 | tcp.set_window(0xffff); 85 | tcp.set_source(local_addr.port()); 86 | tcp.set_destination(remote_addr.port()); 87 | tcp.set_sequence(seq); 88 | tcp.set_acknowledgement(ack); 89 | tcp.set_flags(flags); 90 | tcp.set_data_offset(TCP_HEADER_LEN as u8 / 4 + if wscale { 1 } else { 0 }); 91 | if wscale { 92 | let wscale = tcp::TcpOption::wscale(14); 93 | tcp.set_options(&[tcp::TcpOption::nop(), wscale]); 94 | } 95 | 96 | if let Some(payload) = payload { 97 | tcp.set_payload(payload); 98 | } 99 | 100 | let mut cksm = Checksum::new(); 101 | let ip::IpNextHeaderProtocol(tcp_protocol) = ip::IpNextHeaderProtocols::Tcp; 102 | 103 | match (local_addr, remote_addr) { 104 | (SocketAddr::V4(local), SocketAddr::V4(remote)) => { 105 | cksm.add_bytes(&local.ip().octets()); 106 | cksm.add_bytes(&remote.ip().octets()); 107 | 108 | let mut pseudo = [0u8, tcp_protocol, 0, 0]; 109 | pseudo[2..].copy_from_slice(&(tcp_total_len as u16).to_be_bytes()); 110 | cksm.add_bytes(&pseudo); 111 | } 112 | (SocketAddr::V6(local), SocketAddr::V6(remote)) => { 113 | cksm.add_bytes(&local.ip().octets()); 114 | cksm.add_bytes(&remote.ip().octets()); 115 | 116 | let mut pseudo = [0u8, 0, 0, 0, 0, 0, 0, tcp_protocol]; 117 | pseudo[0..4].copy_from_slice(&(tcp_total_len as u32).to_be_bytes()); 118 | cksm.add_bytes(&pseudo); 119 | } 120 | _ => unreachable!(), 121 | }; 122 | 123 | cksm.add_bytes(tcp.packet()); 124 | tcp.set_checksum(u16::from_be_bytes(cksm.checksum())); 125 | 126 | ip_buf.unsplit(tcp_buf); 127 | ip_buf.freeze() 128 | } 129 | 130 | pub fn parse_ip_packet(buf: &Bytes) -> Option<(IPPacket, tcp::TcpPacket)> { 131 | if buf[0] >> 4 == 4 { 132 | let v4 = ipv4::Ipv4Packet::new(buf).unwrap(); 133 | if v4.get_next_level_protocol() != ip::IpNextHeaderProtocols::Tcp { 134 | return None; 135 | } 136 | 137 | let tcp = tcp::TcpPacket::new(&buf[IPV4_HEADER_LEN..]).unwrap(); 138 | Some((IPPacket::V4(v4), tcp)) 139 | } else if buf[0] >> 4 == 6 { 140 | let v6 = ipv6::Ipv6Packet::new(buf).unwrap(); 141 | if v6.get_next_header() != ip::IpNextHeaderProtocols::Tcp { 142 | return None; 143 | } 144 | 145 | let tcp = tcp::TcpPacket::new(&buf[IPV6_HEADER_LEN..]).unwrap(); 146 | Some((IPPacket::V6(v6), tcp)) 147 | } else { 148 | None 149 | } 150 | } 151 | 152 | #[cfg(all(test, feature = "benchmark"))] 153 | mod benchmarks { 154 | extern crate test; 155 | use super::*; 156 | use test::{black_box, Bencher}; 157 | 158 | #[bench] 159 | fn bench_build_tcp_packet_1460(b: &mut Bencher) { 160 | let local_addr = "127.0.0.1:1234".parse().unwrap(); 161 | let remote_addr = "127.0.0.2:1234".parse().unwrap(); 162 | let payload = black_box([123u8; 1460]); 163 | b.iter(|| { 164 | build_tcp_packet( 165 | local_addr, 166 | remote_addr, 167 | 123, 168 | 456, 169 | tcp::TcpFlags::ACK, 170 | Some(&payload), 171 | ) 172 | }); 173 | } 174 | 175 | #[bench] 176 | fn bench_build_tcp_packet_512(b: &mut Bencher) { 177 | let local_addr = "127.0.0.1:1234".parse().unwrap(); 178 | let remote_addr = "127.0.0.2:1234".parse().unwrap(); 179 | let payload = black_box([123u8; 512]); 180 | b.iter(|| { 181 | build_tcp_packet( 182 | local_addr, 183 | remote_addr, 184 | 123, 185 | 456, 186 | tcp::TcpFlags::ACK, 187 | Some(&payload), 188 | ) 189 | }); 190 | } 191 | 192 | #[bench] 193 | fn bench_build_tcp_packet_128(b: &mut Bencher) { 194 | let local_addr = "127.0.0.1:1234".parse().unwrap(); 195 | let remote_addr = "127.0.0.2:1234".parse().unwrap(); 196 | let payload = black_box([123u8; 128]); 197 | b.iter(|| { 198 | build_tcp_packet( 199 | local_addr, 200 | remote_addr, 201 | 123, 202 | 456, 203 | tcp::TcpFlags::ACK, 204 | Some(&payload), 205 | ) 206 | }); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /images/packet-headers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dndx/phantun/869c79422f1126a9994b756a8ffc7a9405e4946f/images/packet-headers.png -------------------------------------------------------------------------------- /images/phantun-vs-udp2raw-benchmark-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dndx/phantun/869c79422f1126a9994b756a8ffc7a9405e4946f/images/phantun-vs-udp2raw-benchmark-result.png -------------------------------------------------------------------------------- /images/traffic-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dndx/phantun/869c79422f1126a9994b756a8ffc7a9405e4946f/images/traffic-flow.png -------------------------------------------------------------------------------- /phantun/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "phantun" 3 | version = "0.7.0" 4 | edition = "2021" 5 | authors = ["Datong Sun "] 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/dndx/phantun" 8 | readme = "README.md" 9 | description = """ 10 | Transforms UDP stream into (fake) TCP streams that can go through 11 | Layer 3 & Layer 4 (NAPT) firewalls/NATs. 12 | """ 13 | [dependencies] 14 | clap = { version = "4", features = ["cargo"] } 15 | socket2 = { version = "0", features = ["all"] } 16 | fake-tcp = { path = "../fake-tcp", version = "0" } 17 | tokio = { version = "1", features = ["full"] } 18 | tokio-util = "0" 19 | log = "0" 20 | pretty_env_logger = "0" 21 | tokio-tun = "0" 22 | num_cpus = "1" 23 | neli = "0" 24 | nix = { version = "0", features = ["net"] } 25 | -------------------------------------------------------------------------------- /phantun/LICENSE-APACHE: -------------------------------------------------------------------------------- 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 2021-2024 Datong Sun (dndx@idndx.com) 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 | -------------------------------------------------------------------------------- /phantun/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2024 Datong Sun (dndx@idndx.com) 4 | 5 | Permission is hereby granted, free of charge, to any 6 | person obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the 8 | Software without restriction, including without 9 | limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software 12 | is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions 17 | of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /phantun/README.md: -------------------------------------------------------------------------------- 1 | # Phantun 2 | 3 | Client/Server crate, see [Phantun Project README.md](https://github.com/dndx/phantun/blob/main/README.md) for more information. 4 | 5 | ## License 6 | 7 | Copyright 2021 Datong Sun 8 | 9 | Licensed under the Apache License, Version 2.0 or the MIT license 11 | , at your 12 | option. Files in the project may not be 13 | copied, modified, or distributed except according to those terms. 14 | -------------------------------------------------------------------------------- /phantun/src/bin/client.rs: -------------------------------------------------------------------------------- 1 | use clap::{crate_version, Arg, ArgAction, Command}; 2 | use fake_tcp::packet::MAX_PACKET_LEN; 3 | use fake_tcp::{Socket, Stack}; 4 | use log::{debug, error, info}; 5 | use phantun::utils::{assign_ipv6_address, new_udp_reuseport}; 6 | use std::collections::HashMap; 7 | use std::fs; 8 | use std::io; 9 | use std::net::{Ipv4Addr, SocketAddr}; 10 | use std::sync::Arc; 11 | use tokio::sync::{Notify, RwLock}; 12 | use tokio::time; 13 | use tokio_tun::TunBuilder; 14 | use tokio_util::sync::CancellationToken; 15 | 16 | use phantun::UDP_TTL; 17 | 18 | #[tokio::main] 19 | async fn main() -> io::Result<()> { 20 | pretty_env_logger::init(); 21 | 22 | let matches = Command::new("Phantun Client") 23 | .version(crate_version!()) 24 | .author("Datong Sun (github.com/dndx)") 25 | .arg( 26 | Arg::new("local") 27 | .short('l') 28 | .long("local") 29 | .required(true) 30 | .value_name("IP:PORT") 31 | .help("Sets the IP and port where Phantun Client listens for incoming UDP datagrams, IPv6 address need to be specified as: \"[IPv6]:PORT\"") 32 | ) 33 | .arg( 34 | Arg::new("remote") 35 | .short('r') 36 | .long("remote") 37 | .required(true) 38 | .value_name("IP or HOST NAME:PORT") 39 | .help("Sets the address or host name and port where Phantun Client connects to Phantun Server, IPv6 address need to be specified as: \"[IPv6]:PORT\"") 40 | ) 41 | .arg( 42 | Arg::new("tun") 43 | .long("tun") 44 | .required(false) 45 | .value_name("tunX") 46 | .help("Sets the Tun interface name, if absent, pick the next available name") 47 | .default_value("") 48 | ) 49 | .arg( 50 | Arg::new("tun_local") 51 | .long("tun-local") 52 | .required(false) 53 | .value_name("IP") 54 | .help("Sets the Tun interface IPv4 local address (O/S's end)") 55 | .default_value("192.168.200.1") 56 | ) 57 | .arg( 58 | Arg::new("tun_peer") 59 | .long("tun-peer") 60 | .required(false) 61 | .value_name("IP") 62 | .help("Sets the Tun interface IPv4 destination (peer) address (Phantun Client's end). \ 63 | You will need to setup SNAT/MASQUERADE rules on your Internet facing interface \ 64 | in order for Phantun Client to connect to Phantun Server") 65 | .default_value("192.168.200.2") 66 | ) 67 | .arg( 68 | Arg::new("ipv4_only") 69 | .long("ipv4-only") 70 | .short('4') 71 | .required(false) 72 | .help("Only use IPv4 address when connecting to remote") 73 | .action(ArgAction::SetTrue) 74 | .conflicts_with_all(["tun_local6", "tun_peer6"]), 75 | ) 76 | .arg( 77 | Arg::new("tun_local6") 78 | .long("tun-local6") 79 | .required(false) 80 | .value_name("IP") 81 | .help("Sets the Tun interface IPv6 local address (O/S's end)") 82 | .default_value("fcc8::1") 83 | ) 84 | .arg( 85 | Arg::new("tun_peer6") 86 | .long("tun-peer6") 87 | .required(false) 88 | .value_name("IP") 89 | .help("Sets the Tun interface IPv6 destination (peer) address (Phantun Client's end). \ 90 | You will need to setup SNAT/MASQUERADE rules on your Internet facing interface \ 91 | in order for Phantun Client to connect to Phantun Server") 92 | .default_value("fcc8::2") 93 | ) 94 | .arg( 95 | Arg::new("handshake_packet") 96 | .long("handshake-packet") 97 | .required(false) 98 | .value_name("PATH") 99 | .help("Specify a file, which, after TCP handshake, its content will be sent as the \ 100 | first data packet to the server.\n\ 101 | Note: ensure this file's size does not exceed the MTU of the outgoing interface. \ 102 | The content is always sent out in a single packet and will not be further segmented") 103 | ) 104 | .get_matches(); 105 | 106 | let local_addr: SocketAddr = matches 107 | .get_one::("local") 108 | .unwrap() 109 | .parse() 110 | .expect("bad local address"); 111 | 112 | let ipv4_only = matches.get_flag("ipv4_only"); 113 | 114 | let remote_addr = tokio::net::lookup_host(matches.get_one::("remote").unwrap()) 115 | .await 116 | .expect("bad remote address or host") 117 | .find(|addr| !ipv4_only || addr.is_ipv4()) 118 | .expect("unable to resolve remote host name"); 119 | info!("Remote address is: {}", remote_addr); 120 | 121 | let tun_local: Ipv4Addr = matches 122 | .get_one::("tun_local") 123 | .unwrap() 124 | .parse() 125 | .expect("bad local address for Tun interface"); 126 | let tun_peer: Ipv4Addr = matches 127 | .get_one::("tun_peer") 128 | .unwrap() 129 | .parse() 130 | .expect("bad peer address for Tun interface"); 131 | 132 | let (tun_local6, tun_peer6) = if matches.get_flag("ipv4_only") { 133 | (None, None) 134 | } else { 135 | ( 136 | matches 137 | .get_one::("tun_local6") 138 | .map(|v| v.parse().expect("bad local address for Tun interface")), 139 | matches 140 | .get_one::("tun_peer6") 141 | .map(|v| v.parse().expect("bad peer address for Tun interface")), 142 | ) 143 | }; 144 | 145 | let tun_name = matches.get_one::("tun").unwrap(); 146 | let handshake_packet: Option> = matches 147 | .get_one::("handshake_packet") 148 | .map(fs::read) 149 | .transpose()?; 150 | 151 | let num_cpus = num_cpus::get(); 152 | info!("{} cores available", num_cpus); 153 | 154 | let tun = TunBuilder::new() 155 | .name(tun_name) // if name is empty, then it is set by kernel. 156 | .up() // or set it up manually using `sudo ip link set up`. 157 | .address(tun_local) 158 | .destination(tun_peer) 159 | .queues(num_cpus) 160 | .build() 161 | .unwrap(); 162 | 163 | if remote_addr.is_ipv6() { 164 | assign_ipv6_address(tun[0].name(), tun_local6.unwrap(), tun_peer6.unwrap()); 165 | } 166 | 167 | info!("Created TUN device {}", tun[0].name()); 168 | 169 | let udp_sock = Arc::new(new_udp_reuseport(local_addr)); 170 | let connections = Arc::new(RwLock::new(HashMap::>::new())); 171 | 172 | let mut stack = Stack::new(tun, tun_peer, tun_peer6); 173 | 174 | let main_loop = tokio::spawn(async move { 175 | let mut buf_r = [0u8; MAX_PACKET_LEN]; 176 | 177 | loop { 178 | let (size, addr) = udp_sock.recv_from(&mut buf_r).await?; 179 | // seen UDP packet to listening socket, this means: 180 | // 1. It is a new UDP connection, or 181 | // 2. It is some extra packets not filtered by more specific 182 | // connected UDP socket yet 183 | if let Some(sock) = connections.read().await.get(&addr) { 184 | sock.send(&buf_r[..size]).await; 185 | continue; 186 | } 187 | 188 | info!("New UDP client from {}", addr); 189 | let sock = stack.connect(remote_addr).await; 190 | if sock.is_none() { 191 | error!("Unable to connect to remote {}", remote_addr); 192 | continue; 193 | } 194 | 195 | let sock = Arc::new(sock.unwrap()); 196 | if let Some(ref p) = handshake_packet { 197 | if sock.send(p).await.is_none() { 198 | error!("Failed to send handshake packet to remote, closing connection."); 199 | continue; 200 | } 201 | 202 | debug!("Sent handshake packet to: {}", sock); 203 | } 204 | 205 | // send first packet 206 | if sock.send(&buf_r[..size]).await.is_none() { 207 | continue; 208 | } 209 | 210 | assert!(connections 211 | .write() 212 | .await 213 | .insert(addr, sock.clone()) 214 | .is_none()); 215 | debug!("inserted fake TCP socket into connection table"); 216 | 217 | // spawn "fastpath" UDP socket and task, this will offload main task 218 | // from forwarding UDP packets 219 | 220 | let packet_received = Arc::new(Notify::new()); 221 | let quit = CancellationToken::new(); 222 | 223 | for i in 0..num_cpus { 224 | let sock = sock.clone(); 225 | let quit = quit.clone(); 226 | let packet_received = packet_received.clone(); 227 | 228 | tokio::spawn(async move { 229 | let mut buf_udp = [0u8; MAX_PACKET_LEN]; 230 | let mut buf_tcp = [0u8; MAX_PACKET_LEN]; 231 | let udp_sock = new_udp_reuseport(local_addr); 232 | udp_sock.connect(addr).await.unwrap(); 233 | 234 | loop { 235 | tokio::select! { 236 | Ok(size) = udp_sock.recv(&mut buf_udp) => { 237 | if sock.send(&buf_udp[..size]).await.is_none() { 238 | debug!("removed fake TCP socket from connections table"); 239 | quit.cancel(); 240 | return; 241 | } 242 | 243 | packet_received.notify_one(); 244 | }, 245 | res = sock.recv(&mut buf_tcp) => { 246 | match res { 247 | Some(size) => { 248 | if size > 0 { 249 | if let Err(e) = udp_sock.send(&buf_tcp[..size]).await { 250 | error!("Unable to send UDP packet to {}: {}, closing connection", e, addr); 251 | quit.cancel(); 252 | return; 253 | } 254 | } 255 | }, 256 | None => { 257 | debug!("removed fake TCP socket from connections table"); 258 | quit.cancel(); 259 | return; 260 | }, 261 | } 262 | 263 | packet_received.notify_one(); 264 | }, 265 | _ = quit.cancelled() => { 266 | debug!("worker {} terminated", i); 267 | return; 268 | }, 269 | }; 270 | } 271 | }); 272 | } 273 | 274 | let connections = connections.clone(); 275 | tokio::spawn(async move { 276 | loop { 277 | let read_timeout = time::sleep(UDP_TTL); 278 | let packet_received_fut = packet_received.notified(); 279 | 280 | tokio::select! { 281 | _ = read_timeout => { 282 | info!("No traffic seen in the last {:?}, closing connection", UDP_TTL); 283 | connections.write().await.remove(&addr); 284 | debug!("removed fake TCP socket from connections table"); 285 | 286 | quit.cancel(); 287 | return; 288 | }, 289 | _ = quit.cancelled() => { 290 | connections.write().await.remove(&addr); 291 | debug!("removed fake TCP socket from connections table"); 292 | return; 293 | }, 294 | _ = packet_received_fut => {}, 295 | } 296 | } 297 | }); 298 | } 299 | }); 300 | 301 | tokio::join!(main_loop).0.unwrap() 302 | } 303 | -------------------------------------------------------------------------------- /phantun/src/bin/server.rs: -------------------------------------------------------------------------------- 1 | use clap::{crate_version, Arg, ArgAction, Command}; 2 | use fake_tcp::packet::MAX_PACKET_LEN; 3 | use fake_tcp::Stack; 4 | use log::{debug, error, info}; 5 | use phantun::utils::{assign_ipv6_address, new_udp_reuseport}; 6 | use std::fs; 7 | use std::io; 8 | use std::net::Ipv4Addr; 9 | use std::sync::Arc; 10 | use tokio::net::UdpSocket; 11 | use tokio::sync::Notify; 12 | use tokio::time; 13 | use tokio_tun::TunBuilder; 14 | use tokio_util::sync::CancellationToken; 15 | 16 | use phantun::UDP_TTL; 17 | 18 | #[tokio::main] 19 | async fn main() -> io::Result<()> { 20 | pretty_env_logger::init(); 21 | 22 | let matches = Command::new("Phantun Server") 23 | .version(crate_version!()) 24 | .author("Datong Sun (github.com/dndx)") 25 | .arg( 26 | Arg::new("local") 27 | .short('l') 28 | .long("local") 29 | .required(true) 30 | .value_name("PORT") 31 | .help("Sets the port where Phantun Server listens for incoming Phantun Client TCP connections") 32 | ) 33 | .arg( 34 | Arg::new("remote") 35 | .short('r') 36 | .long("remote") 37 | .required(true) 38 | .value_name("IP or HOST NAME:PORT") 39 | .help("Sets the address or host name and port where Phantun Server forwards UDP packets to, IPv6 address need to be specified as: \"[IPv6]:PORT\"") 40 | ) 41 | .arg( 42 | Arg::new("tun") 43 | .long("tun") 44 | .required(false) 45 | .value_name("tunX") 46 | .help("Sets the Tun interface name, if absent, pick the next available name") 47 | .default_value("") 48 | ) 49 | .arg( 50 | Arg::new("tun_local") 51 | .long("tun-local") 52 | .required(false) 53 | .value_name("IP") 54 | .help("Sets the Tun interface local address (O/S's end)") 55 | .default_value("192.168.201.1") 56 | ) 57 | .arg( 58 | Arg::new("tun_peer") 59 | .long("tun-peer") 60 | .required(false) 61 | .value_name("IP") 62 | .help("Sets the Tun interface destination (peer) address (Phantun Server's end). \ 63 | You will need to setup DNAT rules to this address in order for Phantun Server \ 64 | to accept TCP traffic from Phantun Client") 65 | .default_value("192.168.201.2") 66 | ) 67 | .arg( 68 | Arg::new("ipv4_only") 69 | .long("ipv4-only") 70 | .short('4') 71 | .required(false) 72 | .help("Do not assign IPv6 addresses to Tun interface") 73 | .action(ArgAction::SetTrue) 74 | .conflicts_with_all(["tun_local6", "tun_peer6"]), 75 | ) 76 | .arg( 77 | Arg::new("tun_local6") 78 | .long("tun-local6") 79 | .required(false) 80 | .value_name("IP") 81 | .help("Sets the Tun interface IPv6 local address (O/S's end)") 82 | .default_value("fcc9::1") 83 | ) 84 | .arg( 85 | Arg::new("tun_peer6") 86 | .long("tun-peer6") 87 | .required(false) 88 | .value_name("IP") 89 | .help("Sets the Tun interface IPv6 destination (peer) address (Phantun Client's end). \ 90 | You will need to setup SNAT/MASQUERADE rules on your Internet facing interface \ 91 | in order for Phantun Client to connect to Phantun Server") 92 | .default_value("fcc9::2") 93 | ) 94 | .arg( 95 | Arg::new("handshake_packet") 96 | .long("handshake-packet") 97 | .required(false) 98 | .value_name("PATH") 99 | .help("Specify a file, which, after TCP handshake, its content will be sent as the \ 100 | first data packet to the client.\n\ 101 | Note: ensure this file's size does not exceed the MTU of the outgoing interface. \ 102 | The content is always sent out in a single packet and will not be further segmented") 103 | ) 104 | .get_matches(); 105 | 106 | let local_port: u16 = matches 107 | .get_one::("local") 108 | .unwrap() 109 | .parse() 110 | .expect("bad local port"); 111 | 112 | let remote_addr = tokio::net::lookup_host(matches.get_one::("remote").unwrap()) 113 | .await 114 | .expect("bad remote address or host") 115 | .next() 116 | .expect("unable to resolve remote host name"); 117 | 118 | info!("Remote address is: {}", remote_addr); 119 | 120 | let tun_local: Ipv4Addr = matches 121 | .get_one::("tun_local") 122 | .unwrap() 123 | .parse() 124 | .expect("bad local address for Tun interface"); 125 | let tun_peer: Ipv4Addr = matches 126 | .get_one::("tun_peer") 127 | .unwrap() 128 | .parse() 129 | .expect("bad peer address for Tun interface"); 130 | 131 | let (tun_local6, tun_peer6) = if matches.get_flag("ipv4_only") { 132 | (None, None) 133 | } else { 134 | ( 135 | matches 136 | .get_one::("tun_local6") 137 | .map(|v| v.parse().expect("bad local address for Tun interface")), 138 | matches 139 | .get_one::("tun_peer6") 140 | .map(|v| v.parse().expect("bad peer address for Tun interface")), 141 | ) 142 | }; 143 | 144 | let tun_name = matches.get_one::("tun").unwrap(); 145 | let handshake_packet: Option> = matches 146 | .get_one::("handshake_packet") 147 | .map(fs::read) 148 | .transpose()?; 149 | 150 | let num_cpus = num_cpus::get(); 151 | info!("{} cores available", num_cpus); 152 | 153 | let tun = TunBuilder::new() 154 | .name(tun_name) // if name is empty, then it is set by kernel. 155 | .up() // or set it up manually using `sudo ip link set up`. 156 | .address(tun_local) 157 | .destination(tun_peer) 158 | .queues(num_cpus) 159 | .build() 160 | .unwrap(); 161 | 162 | if let (Some(tun_local6), Some(tun_peer6)) = (tun_local6, tun_peer6) { 163 | assign_ipv6_address(tun[0].name(), tun_local6, tun_peer6); 164 | } 165 | 166 | info!("Created TUN device {}", tun[0].name()); 167 | 168 | //thread::sleep(time::Duration::from_secs(5)); 169 | let mut stack = Stack::new(tun, tun_local, tun_local6); 170 | stack.listen(local_port); 171 | info!("Listening on {}", local_port); 172 | 173 | let main_loop = tokio::spawn(async move { 174 | let mut buf_udp = [0u8; MAX_PACKET_LEN]; 175 | let mut buf_tcp = [0u8; MAX_PACKET_LEN]; 176 | 177 | loop { 178 | let sock = Arc::new(stack.accept().await); 179 | info!("New connection: {}", sock); 180 | if let Some(ref p) = handshake_packet { 181 | if sock.send(p).await.is_none() { 182 | error!("Failed to send handshake packet to remote, closing connection."); 183 | continue; 184 | } 185 | 186 | debug!("Sent handshake packet to: {}", sock); 187 | } 188 | 189 | let packet_received = Arc::new(Notify::new()); 190 | let quit = CancellationToken::new(); 191 | let udp_sock = UdpSocket::bind(if remote_addr.is_ipv4() { 192 | "0.0.0.0:0" 193 | } else { 194 | "[::]:0" 195 | }) 196 | .await?; 197 | let local_addr = udp_sock.local_addr()?; 198 | drop(udp_sock); 199 | 200 | for i in 0..num_cpus { 201 | let sock = sock.clone(); 202 | let quit = quit.clone(); 203 | let packet_received = packet_received.clone(); 204 | let udp_sock = new_udp_reuseport(local_addr); 205 | 206 | tokio::spawn(async move { 207 | udp_sock.connect(remote_addr).await.unwrap(); 208 | 209 | loop { 210 | tokio::select! { 211 | Ok(size) = udp_sock.recv(&mut buf_udp) => { 212 | if sock.send(&buf_udp[..size]).await.is_none() { 213 | quit.cancel(); 214 | return; 215 | } 216 | 217 | packet_received.notify_one(); 218 | }, 219 | res = sock.recv(&mut buf_tcp) => { 220 | match res { 221 | Some(size) => { 222 | if size > 0 { 223 | if let Err(e) = udp_sock.send(&buf_tcp[..size]).await { 224 | error!("Unable to send UDP packet to {}: {}, closing connection", e, remote_addr); 225 | quit.cancel(); 226 | return; 227 | } 228 | } 229 | }, 230 | None => { 231 | quit.cancel(); 232 | return; 233 | }, 234 | } 235 | 236 | packet_received.notify_one(); 237 | }, 238 | _ = quit.cancelled() => { 239 | debug!("worker {} terminated", i); 240 | return; 241 | }, 242 | }; 243 | } 244 | }); 245 | } 246 | 247 | tokio::spawn(async move { 248 | loop { 249 | let read_timeout = time::sleep(UDP_TTL); 250 | let packet_received_fut = packet_received.notified(); 251 | 252 | tokio::select! { 253 | _ = read_timeout => { 254 | info!("No traffic seen in the last {:?}, closing connection", UDP_TTL); 255 | 256 | quit.cancel(); 257 | return; 258 | }, 259 | _ = packet_received_fut => {}, 260 | } 261 | } 262 | }); 263 | } 264 | }); 265 | 266 | tokio::join!(main_loop).0.unwrap() 267 | } 268 | -------------------------------------------------------------------------------- /phantun/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | pub mod utils; 4 | 5 | pub const UDP_TTL: Duration = Duration::from_secs(180); 6 | -------------------------------------------------------------------------------- /phantun/src/utils.rs: -------------------------------------------------------------------------------- 1 | use neli::{ 2 | consts::{ 3 | nl::{NlmF, NlmFFlags}, 4 | rtnl::{Ifa, IfaFFlags, RtAddrFamily, Rtm}, 5 | socket::NlFamily, 6 | }, 7 | nl::{NlPayload, Nlmsghdr}, 8 | rtnl::{Ifaddrmsg, Rtattr}, 9 | socket::NlSocketHandle, 10 | types::RtBuffer, 11 | }; 12 | use std::net::{Ipv6Addr, SocketAddr}; 13 | use tokio::net::UdpSocket; 14 | 15 | pub fn new_udp_reuseport(local_addr: SocketAddr) -> UdpSocket { 16 | let udp_sock = socket2::Socket::new( 17 | if local_addr.is_ipv4() { 18 | socket2::Domain::IPV4 19 | } else { 20 | socket2::Domain::IPV6 21 | }, 22 | socket2::Type::DGRAM, 23 | None, 24 | ) 25 | .unwrap(); 26 | udp_sock.set_reuse_port(true).unwrap(); 27 | // from tokio-rs/mio/blob/master/src/sys/unix/net.rs 28 | udp_sock.set_cloexec(true).unwrap(); 29 | udp_sock.set_nonblocking(true).unwrap(); 30 | udp_sock.bind(&socket2::SockAddr::from(local_addr)).unwrap(); 31 | let udp_sock: std::net::UdpSocket = udp_sock.into(); 32 | udp_sock.try_into().unwrap() 33 | } 34 | 35 | pub fn assign_ipv6_address(device_name: &str, local: Ipv6Addr, peer: Ipv6Addr) { 36 | let index = nix::net::if_::if_nametoindex(device_name).unwrap(); 37 | 38 | let mut rtnl = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); 39 | let mut rtattrs = RtBuffer::new(); 40 | rtattrs.push(Rtattr::new(None, Ifa::Local, &local.octets()[..]).unwrap()); 41 | rtattrs.push(Rtattr::new(None, Ifa::Address, &peer.octets()[..]).unwrap()); 42 | 43 | let ifaddrmsg = Ifaddrmsg { 44 | ifa_family: RtAddrFamily::Inet6, 45 | ifa_prefixlen: 128, 46 | ifa_flags: IfaFFlags::empty(), 47 | ifa_scope: 0, 48 | ifa_index: index as i32, 49 | rtattrs, 50 | }; 51 | let nl_header = Nlmsghdr::new( 52 | None, 53 | Rtm::Newaddr, 54 | NlmFFlags::new(&[NlmF::Request]), 55 | None, 56 | None, 57 | NlPayload::Payload(ifaddrmsg), 58 | ); 59 | rtnl.send(nl_header).unwrap(); 60 | } 61 | --------------------------------------------------------------------------------