├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets └── example-xray.json ├── src ├── guards.rs └── main.rs └── test └── run_all_tests.sh /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: check and test 4 | 5 | jobs: 6 | cargo-check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | profile: minimal 13 | toolchain: stable 14 | override: true 15 | - uses: actions-rs/cargo@v1 16 | with: 17 | command: check 18 | 19 | cargo-test: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | toolchain: stable 27 | override: true 28 | - uses: actions-rs/cargo@v1 29 | with: 30 | command: test 31 | 32 | e2e-test: 33 | runs-on: ubuntu-latest 34 | defaults: 35 | run: 36 | shell: bash -l {0} 37 | 38 | steps: 39 | - name: Checkout Repository 40 | uses: actions/checkout@v3 41 | 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | override: true 47 | 48 | - name: Install Dependencies 49 | run: | 50 | sudo apt-get update 51 | sudo apt-get install -y iptables iproute2 socat curl dnsutils cgroup-tools 52 | 53 | - name: Build cproxy 54 | run: | 55 | cargo build --release 56 | 57 | - name: Prepare Test Environment 58 | run: | 59 | mkdir -p test/logs 60 | chmod +x test/*.sh 61 | sudo install -m 755 target/release/cproxy /usr/local/bin 62 | 63 | - name: Run Redirect Mode Tests 64 | run: | 65 | sudo ./test/run_all_tests.sh 66 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [created] 4 | 5 | jobs: 6 | release: 7 | name: release ${{ matrix.target }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | target: [x86_64-unknown-linux-musl] 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Compile and release 16 | uses: Douile/rust-build.action@latest 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | RUSTTARGET: ${{ matrix.target }} 20 | EXTRA_FILES: "README.md LICENSE" 21 | TOOLCHAIN_VERSION: nightly 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea/ 3 | -------------------------------------------------------------------------------- /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.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 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 = "ansi_term" 31 | version = "0.11.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 34 | dependencies = [ 35 | "winapi", 36 | ] 37 | 38 | [[package]] 39 | name = "atty" 40 | version = "0.2.14" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 43 | dependencies = [ 44 | "hermit-abi 0.1.18", 45 | "libc", 46 | "winapi", 47 | ] 48 | 49 | [[package]] 50 | name = "autocfg" 51 | version = "1.3.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 54 | 55 | [[package]] 56 | name = "backtrace" 57 | version = "0.3.71" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 60 | dependencies = [ 61 | "addr2line", 62 | "cc", 63 | "cfg-if", 64 | "libc", 65 | "miniz_oxide", 66 | "object", 67 | "rustc-demangle", 68 | ] 69 | 70 | [[package]] 71 | name = "bitflags" 72 | version = "1.3.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "2.6.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 81 | 82 | [[package]] 83 | name = "bumpalo" 84 | version = "3.12.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 87 | 88 | [[package]] 89 | name = "cc" 90 | version = "1.1.21" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" 93 | dependencies = [ 94 | "shlex", 95 | ] 96 | 97 | [[package]] 98 | name = "cfg-if" 99 | version = "1.0.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 102 | 103 | [[package]] 104 | name = "cfg_aliases" 105 | version = "0.2.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 108 | 109 | [[package]] 110 | name = "cgroups-rs" 111 | version = "0.3.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "6db7c2f5545da4c12c5701455d9471da5f07db52e49b9cccb4f5512226dd0836" 114 | dependencies = [ 115 | "libc", 116 | "log", 117 | "nix 0.25.1", 118 | "regex", 119 | "thiserror", 120 | ] 121 | 122 | [[package]] 123 | name = "clap" 124 | version = "2.33.3" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 127 | dependencies = [ 128 | "ansi_term", 129 | "atty", 130 | "bitflags 1.3.2", 131 | "strsim", 132 | "textwrap", 133 | "unicode-width", 134 | "vec_map", 135 | ] 136 | 137 | [[package]] 138 | name = "cmd_lib" 139 | version = "1.9.5" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "371c15a3c178d0117091bd84414545309ca979555b1aad573ef591ad58818d41" 142 | dependencies = [ 143 | "cmd_lib_macros", 144 | "env_logger", 145 | "faccess", 146 | "lazy_static", 147 | "log", 148 | "os_pipe", 149 | ] 150 | 151 | [[package]] 152 | name = "cmd_lib_macros" 153 | version = "1.9.5" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "cb844bd05be34d91eb67101329aeba9d3337094c04fd8507d821db7ebb488eaf" 156 | dependencies = [ 157 | "proc-macro-error2", 158 | "proc-macro2", 159 | "quote", 160 | "syn 2.0.72", 161 | ] 162 | 163 | [[package]] 164 | name = "color-eyre" 165 | version = "0.6.3" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 168 | dependencies = [ 169 | "backtrace", 170 | "color-spantrace", 171 | "eyre", 172 | "indenter", 173 | "once_cell", 174 | "owo-colors", 175 | "tracing-error", 176 | ] 177 | 178 | [[package]] 179 | name = "color-spantrace" 180 | version = "0.2.1" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 183 | dependencies = [ 184 | "once_cell", 185 | "owo-colors", 186 | "tracing-core", 187 | "tracing-error", 188 | ] 189 | 190 | [[package]] 191 | name = "cproxy" 192 | version = "4.2.2" 193 | dependencies = [ 194 | "cgroups-rs", 195 | "cmd_lib", 196 | "color-eyre", 197 | "ctrlc", 198 | "eyre", 199 | "flume", 200 | "nix 0.29.0", 201 | "structopt", 202 | "tracing", 203 | "tracing-subscriber", 204 | "with_drop", 205 | ] 206 | 207 | [[package]] 208 | name = "ctrlc" 209 | version = "3.4.5" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" 212 | dependencies = [ 213 | "nix 0.29.0", 214 | "windows-sys 0.59.0", 215 | ] 216 | 217 | [[package]] 218 | name = "env_logger" 219 | version = "0.10.2" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 222 | dependencies = [ 223 | "humantime", 224 | "is-terminal", 225 | "log", 226 | "regex", 227 | "termcolor", 228 | ] 229 | 230 | [[package]] 231 | name = "eyre" 232 | version = "0.6.12" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 235 | dependencies = [ 236 | "indenter", 237 | "once_cell", 238 | ] 239 | 240 | [[package]] 241 | name = "faccess" 242 | version = "0.2.4" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" 245 | dependencies = [ 246 | "bitflags 1.3.2", 247 | "libc", 248 | "winapi", 249 | ] 250 | 251 | [[package]] 252 | name = "flume" 253 | version = "0.11.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" 256 | dependencies = [ 257 | "futures-core", 258 | "futures-sink", 259 | "nanorand", 260 | "spin", 261 | ] 262 | 263 | [[package]] 264 | name = "futures-core" 265 | version = "0.3.14" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" 268 | 269 | [[package]] 270 | name = "futures-sink" 271 | version = "0.3.14" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" 274 | 275 | [[package]] 276 | name = "getrandom" 277 | version = "0.2.5" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" 280 | dependencies = [ 281 | "cfg-if", 282 | "js-sys", 283 | "libc", 284 | "wasi", 285 | "wasm-bindgen", 286 | ] 287 | 288 | [[package]] 289 | name = "gimli" 290 | version = "0.28.1" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 293 | 294 | [[package]] 295 | name = "heck" 296 | version = "0.3.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" 299 | dependencies = [ 300 | "unicode-segmentation", 301 | ] 302 | 303 | [[package]] 304 | name = "hermit-abi" 305 | version = "0.1.18" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 308 | dependencies = [ 309 | "libc", 310 | ] 311 | 312 | [[package]] 313 | name = "hermit-abi" 314 | version = "0.3.9" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 317 | 318 | [[package]] 319 | name = "humantime" 320 | version = "2.1.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 323 | 324 | [[package]] 325 | name = "indenter" 326 | version = "0.3.3" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 329 | 330 | [[package]] 331 | name = "is-terminal" 332 | version = "0.4.12" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" 335 | dependencies = [ 336 | "hermit-abi 0.3.9", 337 | "libc", 338 | "windows-sys 0.52.0", 339 | ] 340 | 341 | [[package]] 342 | name = "js-sys" 343 | version = "0.3.50" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" 346 | dependencies = [ 347 | "wasm-bindgen", 348 | ] 349 | 350 | [[package]] 351 | name = "lazy_static" 352 | version = "1.4.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 355 | 356 | [[package]] 357 | name = "libc" 358 | version = "0.2.155" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 361 | 362 | [[package]] 363 | name = "lock_api" 364 | version = "0.4.3" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" 367 | dependencies = [ 368 | "scopeguard", 369 | ] 370 | 371 | [[package]] 372 | name = "log" 373 | version = "0.4.22" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 376 | 377 | [[package]] 378 | name = "matchers" 379 | version = "0.1.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 382 | dependencies = [ 383 | "regex-automata 0.1.10", 384 | ] 385 | 386 | [[package]] 387 | name = "memchr" 388 | version = "2.7.4" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 391 | 392 | [[package]] 393 | name = "miniz_oxide" 394 | version = "0.7.4" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 397 | dependencies = [ 398 | "adler", 399 | ] 400 | 401 | [[package]] 402 | name = "nanorand" 403 | version = "0.7.0" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 406 | dependencies = [ 407 | "getrandom", 408 | ] 409 | 410 | [[package]] 411 | name = "nix" 412 | version = "0.25.1" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" 415 | dependencies = [ 416 | "autocfg", 417 | "bitflags 1.3.2", 418 | "cfg-if", 419 | "libc", 420 | ] 421 | 422 | [[package]] 423 | name = "nix" 424 | version = "0.29.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 427 | dependencies = [ 428 | "bitflags 2.6.0", 429 | "cfg-if", 430 | "cfg_aliases", 431 | "libc", 432 | ] 433 | 434 | [[package]] 435 | name = "nu-ansi-term" 436 | version = "0.46.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 439 | dependencies = [ 440 | "overload", 441 | "winapi", 442 | ] 443 | 444 | [[package]] 445 | name = "object" 446 | version = "0.32.2" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 449 | dependencies = [ 450 | "memchr", 451 | ] 452 | 453 | [[package]] 454 | name = "once_cell" 455 | version = "1.19.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 458 | 459 | [[package]] 460 | name = "os_pipe" 461 | version = "1.2.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" 464 | dependencies = [ 465 | "libc", 466 | "windows-sys 0.59.0", 467 | ] 468 | 469 | [[package]] 470 | name = "overload" 471 | version = "0.1.1" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 474 | 475 | [[package]] 476 | name = "owo-colors" 477 | version = "3.5.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 480 | 481 | [[package]] 482 | name = "pin-project-lite" 483 | version = "0.2.14" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 486 | 487 | [[package]] 488 | name = "proc-macro-error" 489 | version = "1.0.4" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 492 | dependencies = [ 493 | "proc-macro-error-attr", 494 | "proc-macro2", 495 | "quote", 496 | "syn 1.0.67", 497 | "version_check", 498 | ] 499 | 500 | [[package]] 501 | name = "proc-macro-error-attr" 502 | version = "1.0.4" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 505 | dependencies = [ 506 | "proc-macro2", 507 | "quote", 508 | "version_check", 509 | ] 510 | 511 | [[package]] 512 | name = "proc-macro-error-attr2" 513 | version = "2.0.0" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 516 | dependencies = [ 517 | "proc-macro2", 518 | "quote", 519 | ] 520 | 521 | [[package]] 522 | name = "proc-macro-error2" 523 | version = "2.0.1" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 526 | dependencies = [ 527 | "proc-macro-error-attr2", 528 | "proc-macro2", 529 | "quote", 530 | "syn 2.0.72", 531 | ] 532 | 533 | [[package]] 534 | name = "proc-macro2" 535 | version = "1.0.86" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 538 | dependencies = [ 539 | "unicode-ident", 540 | ] 541 | 542 | [[package]] 543 | name = "quote" 544 | version = "1.0.36" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 547 | dependencies = [ 548 | "proc-macro2", 549 | ] 550 | 551 | [[package]] 552 | name = "regex" 553 | version = "1.10.5" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 556 | dependencies = [ 557 | "aho-corasick", 558 | "memchr", 559 | "regex-automata 0.4.7", 560 | "regex-syntax 0.8.4", 561 | ] 562 | 563 | [[package]] 564 | name = "regex-automata" 565 | version = "0.1.10" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 568 | dependencies = [ 569 | "regex-syntax 0.6.22", 570 | ] 571 | 572 | [[package]] 573 | name = "regex-automata" 574 | version = "0.4.7" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 577 | dependencies = [ 578 | "aho-corasick", 579 | "memchr", 580 | "regex-syntax 0.8.4", 581 | ] 582 | 583 | [[package]] 584 | name = "regex-syntax" 585 | version = "0.6.22" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" 588 | 589 | [[package]] 590 | name = "regex-syntax" 591 | version = "0.8.4" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 594 | 595 | [[package]] 596 | name = "rustc-demangle" 597 | version = "0.1.24" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 600 | 601 | [[package]] 602 | name = "scopeguard" 603 | version = "1.1.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 606 | 607 | [[package]] 608 | name = "sharded-slab" 609 | version = "0.1.7" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 612 | dependencies = [ 613 | "lazy_static", 614 | ] 615 | 616 | [[package]] 617 | name = "shlex" 618 | version = "1.3.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 621 | 622 | [[package]] 623 | name = "smallvec" 624 | version = "1.13.2" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 627 | 628 | [[package]] 629 | name = "spin" 630 | version = "0.9.8" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 633 | dependencies = [ 634 | "lock_api", 635 | ] 636 | 637 | [[package]] 638 | name = "strsim" 639 | version = "0.8.0" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 642 | 643 | [[package]] 644 | name = "structopt" 645 | version = "0.3.26" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 648 | dependencies = [ 649 | "clap", 650 | "lazy_static", 651 | "structopt-derive", 652 | ] 653 | 654 | [[package]] 655 | name = "structopt-derive" 656 | version = "0.4.18" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 659 | dependencies = [ 660 | "heck", 661 | "proc-macro-error", 662 | "proc-macro2", 663 | "quote", 664 | "syn 1.0.67", 665 | ] 666 | 667 | [[package]] 668 | name = "syn" 669 | version = "1.0.67" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" 672 | dependencies = [ 673 | "proc-macro2", 674 | "quote", 675 | "unicode-xid", 676 | ] 677 | 678 | [[package]] 679 | name = "syn" 680 | version = "2.0.72" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" 683 | dependencies = [ 684 | "proc-macro2", 685 | "quote", 686 | "unicode-ident", 687 | ] 688 | 689 | [[package]] 690 | name = "termcolor" 691 | version = "1.4.1" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 694 | dependencies = [ 695 | "winapi-util", 696 | ] 697 | 698 | [[package]] 699 | name = "textwrap" 700 | version = "0.11.0" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 703 | dependencies = [ 704 | "unicode-width", 705 | ] 706 | 707 | [[package]] 708 | name = "thiserror" 709 | version = "1.0.63" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 712 | dependencies = [ 713 | "thiserror-impl", 714 | ] 715 | 716 | [[package]] 717 | name = "thiserror-impl" 718 | version = "1.0.63" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 721 | dependencies = [ 722 | "proc-macro2", 723 | "quote", 724 | "syn 2.0.72", 725 | ] 726 | 727 | [[package]] 728 | name = "thread_local" 729 | version = "1.1.4" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 732 | dependencies = [ 733 | "once_cell", 734 | ] 735 | 736 | [[package]] 737 | name = "tracing" 738 | version = "0.1.41" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 741 | dependencies = [ 742 | "pin-project-lite", 743 | "tracing-attributes", 744 | "tracing-core", 745 | ] 746 | 747 | [[package]] 748 | name = "tracing-attributes" 749 | version = "0.1.28" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 752 | dependencies = [ 753 | "proc-macro2", 754 | "quote", 755 | "syn 2.0.72", 756 | ] 757 | 758 | [[package]] 759 | name = "tracing-core" 760 | version = "0.1.33" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 763 | dependencies = [ 764 | "once_cell", 765 | "valuable", 766 | ] 767 | 768 | [[package]] 769 | name = "tracing-error" 770 | version = "0.2.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 773 | dependencies = [ 774 | "tracing", 775 | "tracing-subscriber", 776 | ] 777 | 778 | [[package]] 779 | name = "tracing-log" 780 | version = "0.2.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 783 | dependencies = [ 784 | "log", 785 | "once_cell", 786 | "tracing-core", 787 | ] 788 | 789 | [[package]] 790 | name = "tracing-subscriber" 791 | version = "0.3.19" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 794 | dependencies = [ 795 | "matchers", 796 | "nu-ansi-term", 797 | "once_cell", 798 | "regex", 799 | "sharded-slab", 800 | "smallvec", 801 | "thread_local", 802 | "tracing", 803 | "tracing-core", 804 | "tracing-log", 805 | ] 806 | 807 | [[package]] 808 | name = "unicode-ident" 809 | version = "1.0.12" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 812 | 813 | [[package]] 814 | name = "unicode-segmentation" 815 | version = "1.7.1" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 818 | 819 | [[package]] 820 | name = "unicode-width" 821 | version = "0.1.8" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 824 | 825 | [[package]] 826 | name = "unicode-xid" 827 | version = "0.2.1" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 830 | 831 | [[package]] 832 | name = "valuable" 833 | version = "0.1.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 836 | 837 | [[package]] 838 | name = "vec_map" 839 | version = "0.8.2" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 842 | 843 | [[package]] 844 | name = "version_check" 845 | version = "0.9.2" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 848 | 849 | [[package]] 850 | name = "wasi" 851 | version = "0.10.2+wasi-snapshot-preview1" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 854 | 855 | [[package]] 856 | name = "wasm-bindgen" 857 | version = "0.2.73" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" 860 | dependencies = [ 861 | "cfg-if", 862 | "wasm-bindgen-macro", 863 | ] 864 | 865 | [[package]] 866 | name = "wasm-bindgen-backend" 867 | version = "0.2.73" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" 870 | dependencies = [ 871 | "bumpalo", 872 | "lazy_static", 873 | "log", 874 | "proc-macro2", 875 | "quote", 876 | "syn 1.0.67", 877 | "wasm-bindgen-shared", 878 | ] 879 | 880 | [[package]] 881 | name = "wasm-bindgen-macro" 882 | version = "0.2.73" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" 885 | dependencies = [ 886 | "quote", 887 | "wasm-bindgen-macro-support", 888 | ] 889 | 890 | [[package]] 891 | name = "wasm-bindgen-macro-support" 892 | version = "0.2.73" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" 895 | dependencies = [ 896 | "proc-macro2", 897 | "quote", 898 | "syn 1.0.67", 899 | "wasm-bindgen-backend", 900 | "wasm-bindgen-shared", 901 | ] 902 | 903 | [[package]] 904 | name = "wasm-bindgen-shared" 905 | version = "0.2.73" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" 908 | 909 | [[package]] 910 | name = "winapi" 911 | version = "0.3.9" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 914 | dependencies = [ 915 | "winapi-i686-pc-windows-gnu", 916 | "winapi-x86_64-pc-windows-gnu", 917 | ] 918 | 919 | [[package]] 920 | name = "winapi-i686-pc-windows-gnu" 921 | version = "0.4.0" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 924 | 925 | [[package]] 926 | name = "winapi-util" 927 | version = "0.1.8" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 930 | dependencies = [ 931 | "windows-sys 0.52.0", 932 | ] 933 | 934 | [[package]] 935 | name = "winapi-x86_64-pc-windows-gnu" 936 | version = "0.4.0" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 939 | 940 | [[package]] 941 | name = "windows-sys" 942 | version = "0.52.0" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 945 | dependencies = [ 946 | "windows-targets", 947 | ] 948 | 949 | [[package]] 950 | name = "windows-sys" 951 | version = "0.59.0" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 954 | dependencies = [ 955 | "windows-targets", 956 | ] 957 | 958 | [[package]] 959 | name = "windows-targets" 960 | version = "0.52.6" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 963 | dependencies = [ 964 | "windows_aarch64_gnullvm", 965 | "windows_aarch64_msvc", 966 | "windows_i686_gnu", 967 | "windows_i686_gnullvm", 968 | "windows_i686_msvc", 969 | "windows_x86_64_gnu", 970 | "windows_x86_64_gnullvm", 971 | "windows_x86_64_msvc", 972 | ] 973 | 974 | [[package]] 975 | name = "windows_aarch64_gnullvm" 976 | version = "0.52.6" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 979 | 980 | [[package]] 981 | name = "windows_aarch64_msvc" 982 | version = "0.52.6" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 985 | 986 | [[package]] 987 | name = "windows_i686_gnu" 988 | version = "0.52.6" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 991 | 992 | [[package]] 993 | name = "windows_i686_gnullvm" 994 | version = "0.52.6" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 997 | 998 | [[package]] 999 | name = "windows_i686_msvc" 1000 | version = "0.52.6" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1003 | 1004 | [[package]] 1005 | name = "windows_x86_64_gnu" 1006 | version = "0.52.6" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1009 | 1010 | [[package]] 1011 | name = "windows_x86_64_gnullvm" 1012 | version = "0.52.6" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1015 | 1016 | [[package]] 1017 | name = "windows_x86_64_msvc" 1018 | version = "0.52.6" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1021 | 1022 | [[package]] 1023 | name = "with_drop" 1024 | version = "0.0.3" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "9b11d7ac84ce89d5aeaf2fd53f132f5cc067b697840e742978d8aee3a1a2a9e8" 1027 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cproxy" 3 | version = "4.2.2" 4 | authors = ["Xiangru Lian "] 5 | description = "Transparent proxy built on cgroup net_cls." 6 | homepage = "https://github.com/NOBLES5E/cproxy" 7 | license = "MIT" 8 | edition = "2018" 9 | 10 | [dependencies] 11 | cgroups-rs = "0.3" 12 | cmd_lib = "1.9" 13 | color-eyre = "0.6" 14 | ctrlc = "3.4" 15 | eyre = "0.6" 16 | flume = "0.11" 17 | nix = { version = "0.29", features = ["user"] } 18 | structopt = "0.3" 19 | tracing = "0.1" 20 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 21 | with_drop = "0.0.3" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Shawn L. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |
5 | 6 | [![Crates.io](https://img.shields.io/crates/v/cproxy)](https://crates.io/crates/cproxy) [![CI](https://github.com/NOBLES5E/cproxy/actions/workflows/build.yml/badge.svg)](https://github.com/NOBLES5E/cproxy/actions/workflows/build.yml) ![Crates.io](https://img.shields.io/crates/d/cproxy) ![Crates.io](https://img.shields.io/crates/l/cproxy) 7 | 8 | Ever wished you could make your stubborn programs use a proxy without them even knowing? Well, say hello to `cproxy`. 9 | 10 | ## Key Features 11 | 12 | - Transparent redirection of TCP and UDP traffic 13 | - Support for different proxies per application/process 14 | - Compatible with all programs, including statically linked Go binaries 15 | - DNS request redirection 16 | - Simple usage similar to `proxychains` 17 | - Ability to proxy existing running processes 18 | - Support for both iptables `REDIRECT` and `TPROXY` modes 19 | - DNS server override in `TPROXY` mode 20 | - Network activity tracing using iptables `LOG` target 21 | - Compatible with cgroup v1 and v2 22 | - No background daemon required 23 | - Easy integration with existing software like V2Ray, Xray, and Shadowsocks 24 | 25 | > [!TIP] 26 | > Your proxy should be a transparent proxy port (like V2Ray's `dokodemo-door` inbound or shadowsocks `ss-redir`). But don't panic if you only have a SOCKS5 or HTTP proxy! There are tools that can transform it [faster than Bill Clinton](https://youtu.be/Dv0PxINy2ds?t=570) (check out [transocks](https://github.com/cybozu-go/transocks), [ipt2socks](https://github.com/zfl9/ipt2socks) and [ip2socks-go](https://github.com/lcdbin/ip2socks-go)). 27 | 28 | ## Installation 29 | 30 | You can install by downloading the binary from the [release page](https://github.com/NOBLES5E/cproxy/releases) or install with: `cargo install cproxy`. 31 | 32 | Alternatively, here's a oneliner that downloads the latest release and put it in your `/usr/local/bin/` (for the lazy... I mean, efficient folks): 33 | 34 | ``` 35 | curl -s https://api.github.com/repos/NOBLES5E/cproxy/releases/latest | grep "browser_download_url.*x86_64-unknown-linux-musl.zip" | cut -d : -f 2,3 | tr -d \" | wget -qi - -O /tmp/cproxy.zip && unzip -j /tmp/cproxy.zip cproxy -d /tmp && sudo mv /tmp/cproxy /usr/local/bin/ && sudo chmod +x /usr/local/bin/cproxy && rm /tmp/cproxy.zip 36 | ``` 37 | 38 | ## Usage 39 | 40 | ### Basic Magic Trick: Just Like `proxychains` 41 | 42 | You can launch a new program with `cproxy` with: 43 | 44 | ``` 45 | sudo cproxy --port -- --arg1 --arg2 ... 46 | ``` 47 | 48 | All TCP connections requests will be proxied. If your local transparent proxy support DNS address overriding, you can 49 | also redirect DNS traffic with `--redirect-dns`: 50 | 51 | ``` 52 | sudo cproxy --port --redirect-dns -- --arg1 --arg2 ... 53 | ``` 54 | 55 | For an example setup, see [wiki](https://github.com/NOBLES5E/cproxy/wiki/Example-setup-with-V2Ray). 56 | 57 | > [!NOTE] 58 | > Scared of `sudo` in the command? Well, that's what we need to have the permission to modify cgroup. But don't worry too much, the program you run will still be run under your original user, not as root. `cproxy` automatically drops privileges after setting up the necessary cgroup configurations, ensuring that your program runs with the same permissions as if you had launched it directly. 59 | 60 | ### The TPROXY Twist 61 | 62 | If your system support `tproxy`, you can use `tproxy` with `--mode tproxy`: 63 | 64 | ```bash 65 | sudo cproxy --port --mode tproxy -- --arg1 --arg2 ... 66 | # or for existing process 67 | sudo cproxy --port --mode tproxy --pid 68 | ``` 69 | 70 | With `--mode tproxy`, there are several differences: 71 | 72 | * All UDP traffic are proxied instead of only DNS UDP traffic to port 53. 73 | * Your V2Ray or shadowsocks service should have `tproxy` enabled on the inbound port. For V2Ray, you 74 | need `"tproxy": "tproxy"` as 75 | in [V2Ray Documentation](https://www.v2ray.com/en/configuration/transport.html#sockoptobject). For shadowsocks, you 76 | need `-u` as shown in [shadowsocks manpage](http://manpages.org/ss-redir). 77 | 78 | An example setup can be found [here](https://github.com/NOBLES5E/cproxy/wiki/Example-setup-with-V2Ray). 79 | 80 | Note that when you are using the `tproxy` mode, you can override the DNS server address 81 | with `cproxy --mode tproxy --override-dns ...`. This is useful when you want to use a different 82 | DNS server for a specific application. 83 | 84 | ### Advanced Usage: Proxy an Existing Process 85 | 86 | With `cproxy`, you can even proxy an existing process. This is very handy when you want to proxy existing system 87 | services such as `docker`. To do this, just run 88 | 89 | ``` 90 | sudo cproxy --port --pid 91 | ``` 92 | 93 | The target process will be proxied as long as this `cproxy` command is running. You can press Ctrl-C to stop proxying. 94 | 95 | ### Advanced Usage: Debug a Program's Network Activity with Iptables LOG Target 96 | 97 | With `cproxy`, you can easily debug a program's traffic in netfilter. Just run the program with 98 | 99 | ```bash 100 | sudo cproxy --mode trace 101 | ``` 102 | 103 | You will be able to see log in `dmesg`. Note that this requires a recent enough kernel and iptables. 104 | 105 | ### Advanced Usage: Proxy Specific Cgroup Paths 106 | 107 | `cproxy` allows you to proxy all processes within specific cgroup paths. This is particularly useful for managing groups of related processes without specifying individual PIDs. 108 | 109 | Suppose you have a cgroup at `/sys/fs/cgroup/mygroup` containing several processes you wish to proxy. You can run: 110 | 111 | ```bash 112 | sudo cproxy --port 1080 --cgroup-path /sys/fs/cgroup/mygroup --mode tproxy 113 | ``` 114 | 115 | This command will proxy all TCP and UDP traffic from processes within the `/sys/fs/cgroup/mygroup` cgroup using TPROXY mode on port `1080`. 116 | 117 | ## The Secret Sauce 118 | 119 | `cproxy` simply creates a unique `cgroup` for the proxied program, and redirect its traffic with packet rules. 120 | 121 | ## Limitations 122 | 123 | * `cproxy` requires root access to modify `cgroup`. 124 | * Currently only tested on Linux. 125 | 126 | ## Similar Projects 127 | 128 | There are some awesome existing work: 129 | 130 | * [graftcp](https://github.com/hmgle/graftcp): work on most programs, but cannot proxy UDP (such as DNS) 131 | requests. `graftcp` also has performance hit on the underlying program, since it uses `ptrace`. 132 | * [proxychains](https://github.com/haad/proxychains): easy to use, but not working on static linked programs (such as Go 133 | programs). 134 | * [proxychains-ng](https://github.com/rofl0r/proxychains-ng): similar to proxychains. 135 | * [cgproxy](https://github.com/springzfx/cgproxy): `cgproxy` also uses cgroup to do transparent proxy, and the idea is 136 | similar to `cproxy`'s. There are some differences in UX and system requirements: 137 | * `cgproxy` requires system `cgroup` v2 support, while `cproxy` works with both v1 and v2. 138 | * `cgproxy` requires a background daemon process `cgproxyd` running, while `cproxy` does not. 139 | * `cgproxy` requires `tproxy`, which is optional in `cproxy`. 140 | * `cgproxy` can be used to do global proxy, while `cproxy` does not intended to support global proxy. 141 | -------------------------------------------------------------------------------- /assets/example-xray.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "routing": { 6 | "domainStrategy": "IPIfNonMatch" 7 | }, 8 | "inbounds": [ 9 | { 10 | "listen": "127.0.0.1", 11 | "port": 59999, 12 | "protocol": "dokodemo-door", 13 | "settings": { 14 | "followRedirect": true, 15 | "network": "tcp,udp" 16 | }, 17 | "sniffing": { 18 | "destOverride": [ 19 | "http", 20 | "tls" 21 | ], 22 | "enabled": true, 23 | "streamSettings": { 24 | "sockopt": { 25 | "tproxy": "tproxy" 26 | } 27 | } 28 | } 29 | } 30 | ], 31 | "outbounds": [ 32 | { 33 | "protocol": "freedom", 34 | "tag": "direct" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /src/guards.rs: -------------------------------------------------------------------------------- 1 | use cgroups_rs::cgroup_builder::CgroupBuilder; 2 | use cgroups_rs::{Cgroup, CgroupPid}; 3 | use eyre::Result; 4 | use std::time::Duration; 5 | 6 | #[allow(unused)] 7 | pub struct CGroupGuard { 8 | pub pid: Option, 9 | pub cg: Cgroup, 10 | pub cg_path: String, 11 | pub class_id: u32, 12 | pub hier_v2: bool, 13 | } 14 | 15 | impl CGroupGuard { 16 | pub fn new(pid: u32) -> Result { 17 | let hier = cgroups_rs::hierarchies::auto(); 18 | let hier_v2 = hier.v2(); 19 | let class_id = pid; 20 | let cg_path = format!("cproxy-{}", pid); 21 | let cg: Cgroup = CgroupBuilder::new(cg_path.as_str()) 22 | .network() 23 | .class_id(class_id as u64) 24 | .done() 25 | .build(hier)?; 26 | cg.add_task_by_tgid(CgroupPid::from(pid as u64)) 27 | .expect("add task failed"); 28 | Ok(Self { 29 | pid: Some(pid), 30 | hier_v2, 31 | cg, 32 | cg_path, 33 | class_id, 34 | }) 35 | } 36 | 37 | pub fn from_path(path: &str) -> Result { 38 | let hier = cgroups_rs::hierarchies::auto(); 39 | let hier_v2 = hier.v2(); 40 | // Use path hash as class_id to avoid conflicts 41 | let class_id = { 42 | use std::hash::{Hash, Hasher}; 43 | let mut hasher = std::collections::hash_map::DefaultHasher::new(); 44 | path.hash(&mut hasher); 45 | hasher.finish() as u32 46 | }; 47 | 48 | let cg = CgroupBuilder::new(path) 49 | .network() 50 | .class_id(class_id as u64) 51 | .done() 52 | .build(hier)?; 53 | 54 | Ok(Self { 55 | pid: None, 56 | hier_v2, 57 | cg, 58 | cg_path: path.to_string(), 59 | class_id, 60 | }) 61 | } 62 | } 63 | 64 | impl Drop for CGroupGuard { 65 | fn drop(&mut self) { 66 | for t in self.cg.procs() { 67 | let t_dbg_string = format!("{:?}", t); 68 | if let Err(e) = self.cg.remove_task_by_tgid(t) { 69 | tracing::error!( 70 | "failed to remove process from cgroup. pid: {}. error: {}", 71 | t_dbg_string, 72 | e 73 | ); 74 | } 75 | } 76 | if let Err(e) = self.cg.delete() { 77 | tracing::warn!("failed to delete cgroup. error: {}", e) 78 | } 79 | } 80 | } 81 | 82 | #[allow(unused)] 83 | pub struct RedirectGuard { 84 | port: u32, 85 | output_chain_name: String, 86 | cgroup_guard: CGroupGuard, 87 | redirect_dns: bool, 88 | } 89 | 90 | impl RedirectGuard { 91 | pub fn new( 92 | port: u32, 93 | output_chain_name: &str, 94 | cgroup_guard: CGroupGuard, 95 | redirect_dns: bool, 96 | ) -> Result { 97 | tracing::debug!( 98 | "creating redirect guard on port {}, with redirect_dns: {}", 99 | port, 100 | redirect_dns 101 | ); 102 | let class_id = cgroup_guard.class_id; 103 | let cgroup_path = cgroup_guard.cg_path.as_str(); 104 | (cmd_lib::run_cmd! { 105 | iptables -t nat -N ${output_chain_name}; 106 | iptables -t nat -A OUTPUT -j ${output_chain_name}; 107 | iptables -t nat -A ${output_chain_name} -p udp -o lo -j RETURN; 108 | iptables -t nat -A ${output_chain_name} -p tcp -o lo -j RETURN; 109 | })?; 110 | 111 | if cgroup_guard.hier_v2 { 112 | (cmd_lib::run_cmd! { 113 | iptables -t nat -A ${output_chain_name} -p tcp -m cgroup --path ${cgroup_path} -j REDIRECT --to-ports ${port}; 114 | })?; 115 | if redirect_dns { 116 | (cmd_lib::run_cmd! { 117 | iptables -t nat -A ${output_chain_name} -p udp -m cgroup --path ${cgroup_path} --dport 53 -j REDIRECT --to-ports ${port}; 118 | })?; 119 | } 120 | } else { 121 | (cmd_lib::run_cmd! { 122 | iptables -t nat -A ${output_chain_name} -p tcp -m cgroup --cgroup ${class_id} -j REDIRECT --to-ports ${port}; 123 | })?; 124 | if redirect_dns { 125 | (cmd_lib::run_cmd! { 126 | iptables -t nat -A ${output_chain_name} -p udp -m cgroup --cgroup ${class_id} --dport 53 -j REDIRECT --to-ports ${port}; 127 | })?; 128 | } 129 | } 130 | 131 | Ok(Self { 132 | port, 133 | output_chain_name: output_chain_name.to_owned(), 134 | cgroup_guard, 135 | redirect_dns, 136 | }) 137 | } 138 | } 139 | 140 | impl Drop for RedirectGuard { 141 | fn drop(&mut self) { 142 | let output_chain_name = &self.output_chain_name; 143 | 144 | (cmd_lib::run_cmd! { 145 | iptables -t nat -D OUTPUT -j ${output_chain_name}; 146 | iptables -t nat -F ${output_chain_name}; 147 | iptables -t nat -X ${output_chain_name}; 148 | }) 149 | .expect("drop iptables and cgroup failed"); 150 | } 151 | } 152 | 153 | pub struct IpRuleGuardInner { 154 | fwmark: u32, 155 | table: u32, 156 | guard_thread: std::thread::JoinHandle<()>, 157 | stop_channel: flume::Sender<()>, 158 | } 159 | 160 | #[allow(unused)] 161 | pub struct IpRuleGuard { 162 | inner: Box, 163 | } 164 | 165 | impl IpRuleGuard { 166 | pub fn new(fwmark: u32, table: u32) -> Self { 167 | let (sender, receiver) = flume::unbounded(); 168 | let thread = std::thread::spawn(move || { 169 | (cmd_lib::run_cmd! { 170 | ip rule add fwmark ${fwmark} table ${table}; 171 | ip route add local 0.0.0.0/0 dev lo table ${table}; 172 | }) 173 | .expect("set routing rules failed"); 174 | loop { 175 | if (cmd_lib::run_fun! { ip rule list fwmark ${fwmark} }) 176 | .expect("get routing rules failed") 177 | .is_empty() 178 | { 179 | tracing::warn!("detected disappearing routing policy, possibly due to interruped network, resetting"); 180 | (cmd_lib::run_cmd! { 181 | ip rule add fwmark ${fwmark} table ${table}; 182 | }) 183 | .expect("set routing rules failed"); 184 | } 185 | if receiver.recv_timeout(Duration::from_secs(1)).is_ok() { 186 | break; 187 | } 188 | } 189 | }); 190 | let inner = IpRuleGuardInner { 191 | fwmark, 192 | table, 193 | guard_thread: thread, 194 | stop_channel: sender, 195 | }; 196 | let inner = with_drop::with_drop(inner, |x| { 197 | x.stop_channel.send(()).unwrap(); 198 | x.guard_thread.join().unwrap(); 199 | let mark = x.fwmark; 200 | let table = x.table; 201 | (cmd_lib::run_cmd! { 202 | ip rule delete fwmark ${mark} table ${table}; 203 | ip route delete local 0.0.0.0/0 dev lo table ${table}; 204 | }) 205 | .expect("drop routing rules failed"); 206 | }); 207 | Self { 208 | inner: Box::new(inner), 209 | } 210 | } 211 | } 212 | 213 | #[allow(unused)] 214 | pub struct TProxyGuard { 215 | port: u32, 216 | mark: u32, 217 | output_chain_name: String, 218 | prerouting_chain_name: String, 219 | iprule_guard: IpRuleGuard, 220 | cgroup_guard: CGroupGuard, 221 | override_dns: Option, 222 | } 223 | 224 | impl TProxyGuard { 225 | pub fn new( 226 | port: u32, 227 | mark: u32, 228 | output_chain_name: &str, 229 | prerouting_chain_name: &str, 230 | cgroup_guard: CGroupGuard, 231 | override_dns: Option, 232 | ) -> Result { 233 | let class_id = cgroup_guard.class_id; 234 | let cg_path = cgroup_guard.cg_path.as_str(); 235 | tracing::debug!( 236 | "creating tproxy guard on port {}, with override_dns: {:?}", 237 | port, 238 | override_dns 239 | ); 240 | let iprule_guard = IpRuleGuard::new(mark, mark); 241 | (cmd_lib::run_cmd! { 242 | 243 | iptables -t mangle -N ${prerouting_chain_name}; 244 | iptables -t mangle -A PREROUTING -j ${prerouting_chain_name}; 245 | iptables -t mangle -A ${prerouting_chain_name} -p tcp -o lo -j RETURN; 246 | iptables -t mangle -A ${prerouting_chain_name} -p udp -o lo -j RETURN; 247 | iptables -t mangle -A ${prerouting_chain_name} -p udp -m mark --mark ${mark} -j TPROXY --on-ip 127.0.0.1 --on-port ${port}; 248 | iptables -t mangle -A ${prerouting_chain_name} -p tcp -m mark --mark ${mark} -j TPROXY --on-ip 127.0.0.1 --on-port ${port}; 249 | 250 | iptables -t mangle -N ${output_chain_name}; 251 | iptables -t mangle -A OUTPUT -j ${output_chain_name}; 252 | iptables -t mangle -A ${output_chain_name} -p tcp -o lo -j RETURN; 253 | iptables -t mangle -A ${output_chain_name} -p udp -o lo -j RETURN; 254 | })?; 255 | 256 | if override_dns.is_some() { 257 | (cmd_lib::run_cmd! { 258 | iptables -t nat -N ${output_chain_name}; 259 | iptables -t nat -A OUTPUT -j ${output_chain_name}; 260 | iptables -t nat -A ${output_chain_name} -p udp -o lo -j RETURN; 261 | })?; 262 | } 263 | 264 | if cgroup_guard.hier_v2 { 265 | (cmd_lib::run_cmd! { 266 | iptables -t mangle -A ${output_chain_name} -p tcp -m cgroup --path ${cg_path} -j MARK --set-mark ${mark}; 267 | iptables -t mangle -A ${output_chain_name} -p udp -m cgroup --path ${cg_path} -j MARK --set-mark ${mark}; 268 | })?; 269 | if let Some(override_dns) = &override_dns { 270 | (cmd_lib::run_cmd! { 271 | iptables -t nat -A ${output_chain_name} -p udp -m cgroup --path ${cg_path} --dport 53 -j DNAT --to-destination ${override_dns}; 272 | })?; 273 | } 274 | } else { 275 | (cmd_lib::run_cmd! { 276 | iptables -t mangle -A ${output_chain_name} -p tcp -m cgroup --cgroup ${class_id} -j MARK --set-mark ${mark}; 277 | iptables -t mangle -A ${output_chain_name} -p udp -m cgroup --cgroup ${class_id} -j MARK --set-mark ${mark}; 278 | })?; 279 | if let Some(override_dns) = &override_dns { 280 | (cmd_lib::run_cmd! { 281 | iptables -t nat -A ${output_chain_name} -p udp -m cgroup --cgroup ${class_id} --dport 53 -j DNAT --to-destination ${override_dns}; 282 | })?; 283 | } 284 | } 285 | 286 | Ok(Self { 287 | port, 288 | mark, 289 | output_chain_name: output_chain_name.to_owned(), 290 | prerouting_chain_name: prerouting_chain_name.to_owned(), 291 | iprule_guard, 292 | cgroup_guard, 293 | override_dns, 294 | }) 295 | } 296 | } 297 | 298 | impl Drop for TProxyGuard { 299 | fn drop(&mut self) { 300 | let output_chain_name = &self.output_chain_name; 301 | let prerouting_chain_name = &self.prerouting_chain_name; 302 | 303 | std::thread::sleep(Duration::from_millis(100)); 304 | 305 | (cmd_lib::run_cmd! { 306 | iptables -t mangle -D PREROUTING -j ${prerouting_chain_name}; 307 | iptables -t mangle -F ${prerouting_chain_name}; 308 | iptables -t mangle -X ${prerouting_chain_name}; 309 | 310 | iptables -t mangle -D OUTPUT -j ${output_chain_name}; 311 | iptables -t mangle -F ${output_chain_name}; 312 | iptables -t mangle -X ${output_chain_name}; 313 | }) 314 | .expect("drop iptables and cgroup failed"); 315 | 316 | if self.override_dns.is_some() { 317 | (cmd_lib::run_cmd! { 318 | iptables -t nat -D OUTPUT -j ${output_chain_name}; 319 | iptables -t nat -F ${output_chain_name}; 320 | iptables -t nat -X ${output_chain_name}; 321 | }) 322 | .expect("drop iptables failed"); 323 | } 324 | } 325 | } 326 | 327 | #[allow(unused)] 328 | pub struct TraceGuard { 329 | prerouting_chain_name: String, 330 | output_chain_name: String, 331 | cgroup_guard: CGroupGuard, 332 | } 333 | 334 | impl TraceGuard { 335 | pub fn new( 336 | output_chain_name: &str, 337 | prerouting_chain_name: &str, 338 | cgroup_guard: CGroupGuard, 339 | ) -> Result { 340 | let class_id = cgroup_guard.class_id; 341 | (cmd_lib::run_cmd! { 342 | // iptables -t raw -N ${prerouting_chain_name}; 343 | // iptables -t raw -A PREROUTING -j ${prerouting_chain_name}; 344 | // iptables -t raw -A ${prerouting_chain_name} -p udp -j LOG; 345 | // iptables -t raw -A ${prerouting_chain_name} -p tcp -j LOG; 346 | 347 | iptables -t raw -N ${output_chain_name}; 348 | iptables -t raw -A OUTPUT -j ${output_chain_name}; 349 | iptables -t raw -A ${output_chain_name} -m cgroup --cgroup ${class_id} -p tcp -j LOG; 350 | iptables -t raw -A ${output_chain_name} -m cgroup --cgroup ${class_id} -p udp -j LOG; 351 | })?; 352 | 353 | Ok(Self { 354 | output_chain_name: output_chain_name.to_owned(), 355 | prerouting_chain_name: prerouting_chain_name.to_owned(), 356 | cgroup_guard, 357 | }) 358 | } 359 | } 360 | 361 | impl Drop for TraceGuard { 362 | fn drop(&mut self) { 363 | let output_chain_name = &self.output_chain_name; 364 | let _prerouting_chain_name = &self.prerouting_chain_name; 365 | 366 | std::thread::sleep(Duration::from_millis(100)); 367 | 368 | (cmd_lib::run_cmd! { 369 | // iptables -t raw -D PREROUTING -j ${prerouting_chain_name}; 370 | // iptables -t raw -F ${prerouting_chain_name}; 371 | // iptables -t raw -X ${prerouting_chain_name}; 372 | 373 | iptables -t raw -D OUTPUT -j ${output_chain_name}; 374 | iptables -t raw -F ${output_chain_name}; 375 | iptables -t raw -X ${output_chain_name}; 376 | }) 377 | .expect("drop iptables and cgroup failed"); 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dyn_drop)] 2 | 3 | use crate::guards::TraceGuard; 4 | use eyre::Result; 5 | use guards::{CGroupGuard, RedirectGuard, TProxyGuard}; 6 | use std::os::unix::prelude::CommandExt; 7 | use std::process::ExitStatus; 8 | use std::sync::atomic::{AtomicBool, Ordering}; 9 | use std::sync::Arc; 10 | use std::time::Duration; 11 | use structopt::StructOpt; 12 | 13 | mod guards; 14 | 15 | #[derive(StructOpt, Debug)] 16 | struct Cli { 17 | /// Redirect traffic to specific local port. 18 | #[structopt(long, env = "CPROXY_PORT", default_value = "1080")] 19 | port: u32, 20 | 21 | /// redirect DNS traffic. This option only works with redirect mode 22 | #[structopt(long)] 23 | redirect_dns: bool, 24 | 25 | /// Proxy mode can be `trace` (use iptables TRACE target to debug program network), `tproxy`, or `redirect`. 26 | #[structopt(long, default_value = "redirect")] 27 | mode: String, 28 | 29 | /// Override dns server address. This option only works with tproxy mode 30 | #[structopt(long)] 31 | override_dns: Option, 32 | 33 | /// Proxy an existing process. 34 | #[structopt(long)] 35 | pid: Option, 36 | 37 | /// Proxy specific cgroup paths, can be specified multiple times) 38 | #[structopt(long)] 39 | cgroup_path: Vec, 40 | 41 | #[structopt(subcommand)] 42 | command: Option, 43 | } 44 | 45 | #[derive(StructOpt, Debug)] 46 | enum ChildCommand { 47 | #[structopt(external_subcommand)] 48 | Command(Vec), 49 | } 50 | 51 | fn proxy_new_command(args: &Cli) -> Result { 52 | let pid = std::process::id(); 53 | let ChildCommand::Command(child_command) = &args 54 | .command 55 | .as_ref() 56 | .expect("must have command specified if --pid not provided"); 57 | tracing::info!("subcommand {:?}", child_command); 58 | 59 | let port = args.port; 60 | 61 | let cgroup_guard = CGroupGuard::new(pid)?; 62 | let _guard: Box = match args.mode.as_str() { 63 | "redirect" => { 64 | let output_chain_name = format!("cp_rd_out_{}", pid); 65 | Box::new(RedirectGuard::new( 66 | port, 67 | output_chain_name.as_str(), 68 | cgroup_guard, 69 | args.redirect_dns, 70 | )?) 71 | } 72 | "tproxy" => { 73 | let output_chain_name = format!("cp_tp_out_{}", pid); 74 | let prerouting_chain_name = format!("cp_tp_pre_{}", pid); 75 | let mark = pid; 76 | Box::new(TProxyGuard::new( 77 | port, 78 | mark, 79 | output_chain_name.as_str(), 80 | prerouting_chain_name.as_str(), 81 | cgroup_guard, 82 | args.override_dns.clone(), 83 | )?) 84 | } 85 | "trace" => { 86 | let prerouting_chain_name = format!("cp_tr_pre_{}", pid); 87 | let output_chain_name = format!("cp_tr_out_{}", pid); 88 | Box::new(TraceGuard::new( 89 | output_chain_name.as_str(), 90 | prerouting_chain_name.as_str(), 91 | cgroup_guard, 92 | )?) 93 | } 94 | &_ => { 95 | unimplemented!() 96 | } 97 | }; 98 | 99 | let sudo_uid = std::env::var("SUDO_UID").ok(); 100 | let sudo_gid = std::env::var("SUDO_GID").ok(); 101 | let sudo_home = std::env::var("SUDO_HOME").ok(); 102 | 103 | let original_uid = nix::unistd::getuid(); 104 | let original_gid = nix::unistd::getgid(); 105 | let mut command = std::process::Command::new(&child_command[0]); 106 | if let Some(sudo_uid) = sudo_uid { 107 | command.uid(sudo_uid.parse().expect("invalid uid")); 108 | } 109 | if let Some(sudo_gid) = sudo_gid { 110 | command.gid(sudo_gid.parse().expect("invalid gid")); 111 | } 112 | command.env("CPROXY_ENV", format!("cproxy/{}", port)); 113 | if let Some(sudo_home) = sudo_home { 114 | command.env("HOME", sudo_home); 115 | } 116 | let mut child = command.args(&child_command[1..]).spawn()?; 117 | nix::unistd::seteuid(original_uid)?; 118 | nix::unistd::setegid(original_gid)?; 119 | 120 | ctrlc::set_handler(move || { 121 | println!("received ctrl-c, terminating..."); 122 | })?; 123 | 124 | let exit_status = child.wait()?; 125 | Ok(exit_status) 126 | } 127 | 128 | fn proxy_existing_pid(pid: u32, args: &Cli) -> Result<()> { 129 | let port = args.port; 130 | 131 | let cgroup_guard = CGroupGuard::new(pid)?; 132 | let _guard: Box = match args.mode.as_str() { 133 | "redirect" => { 134 | let output_chain_name = format!("cp_rd_out_{}", pid); 135 | Box::new(RedirectGuard::new( 136 | port, 137 | output_chain_name.as_str(), 138 | cgroup_guard, 139 | args.redirect_dns, 140 | )?) 141 | } 142 | "tproxy" => { 143 | let output_chain_name = format!("cp_tp_out_{}", pid); 144 | let prerouting_chain_name = format!("cp_tp_pre_{}", pid); 145 | let mark = pid; 146 | Box::new(TProxyGuard::new( 147 | port, 148 | mark, 149 | output_chain_name.as_str(), 150 | prerouting_chain_name.as_str(), 151 | cgroup_guard, 152 | args.override_dns.clone(), 153 | )?) 154 | } 155 | "trace" => { 156 | let prerouting_chain_name = format!("cp_tr_pre_{}", pid); 157 | let output_chain_name = format!("cp_tr_out_{}", pid); 158 | Box::new(TraceGuard::new( 159 | output_chain_name.as_str(), 160 | prerouting_chain_name.as_str(), 161 | cgroup_guard, 162 | )?) 163 | } 164 | _ => { 165 | unimplemented!() 166 | } 167 | }; 168 | 169 | let running = Arc::new(AtomicBool::new(true)); 170 | let r = running.clone(); 171 | 172 | ctrlc::set_handler(move || { 173 | println!("received ctrl-c, terminating..."); 174 | r.store(false, Ordering::SeqCst); 175 | })?; 176 | 177 | while running.load(Ordering::SeqCst) { 178 | std::thread::sleep(Duration::from_millis(100)); 179 | } 180 | 181 | Ok(()) 182 | } 183 | 184 | fn proxy_cgroup_paths(paths: Vec, args: &Cli) -> Result<()> { 185 | let port = args.port; 186 | 187 | let mut guards: Vec> = Vec::new(); 188 | 189 | for path in paths { 190 | let cgroup_guard = CGroupGuard::from_path(&path)?; 191 | let guard: Box = match args.mode.as_str() { 192 | "redirect" => { 193 | let output_chain_name = format!("cp_rd_out_{}", cgroup_guard.class_id); 194 | Box::new(RedirectGuard::new( 195 | port, 196 | output_chain_name.as_str(), 197 | cgroup_guard, 198 | args.redirect_dns, 199 | )?) 200 | } 201 | "tproxy" => { 202 | let output_chain_name = format!("cp_tp_out_{}", cgroup_guard.class_id); 203 | let prerouting_chain_name = format!("cp_tp_pre_{}", cgroup_guard.class_id); 204 | let mark = cgroup_guard.class_id; 205 | Box::new(TProxyGuard::new( 206 | port, 207 | mark, 208 | output_chain_name.as_str(), 209 | prerouting_chain_name.as_str(), 210 | cgroup_guard, 211 | args.override_dns.clone(), 212 | )?) 213 | } 214 | "trace" => { 215 | let prerouting_chain_name = format!("cp_tr_pre_{}", cgroup_guard.class_id); 216 | let output_chain_name = format!("cp_tr_out_{}", cgroup_guard.class_id); 217 | Box::new(TraceGuard::new( 218 | output_chain_name.as_str(), 219 | prerouting_chain_name.as_str(), 220 | cgroup_guard, 221 | )?) 222 | } 223 | _ => { 224 | unimplemented!() 225 | } 226 | }; 227 | guards.push(guard); 228 | } 229 | 230 | let running = Arc::new(AtomicBool::new(true)); 231 | let r = running.clone(); 232 | 233 | ctrlc::set_handler(move || { 234 | println!("received ctrl-c, terminating..."); 235 | r.store(false, Ordering::SeqCst); 236 | })?; 237 | 238 | while running.load(Ordering::SeqCst) { 239 | std::thread::sleep(Duration::from_millis(100)); 240 | } 241 | 242 | Ok(()) 243 | } 244 | 245 | fn main() -> Result<()> { 246 | color_eyre::install()?; 247 | tracing_subscriber::fmt() 248 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 249 | .init(); 250 | nix::unistd::seteuid(nix::unistd::Uid::from_raw(0)) 251 | .expect("cproxy failed to seteuid, please run as root"); 252 | nix::unistd::setegid(nix::unistd::Gid::from_raw(0)) 253 | .expect("cproxy failed to seteuid, please run as root"); 254 | let args: Cli = Cli::from_args(); 255 | 256 | if args.cgroup_path.len() > 0 { 257 | proxy_cgroup_paths(args.cgroup_path.clone(), &args)?; 258 | } else { 259 | match args.pid { 260 | None => { 261 | let exit_status = proxy_new_command(&args)?; 262 | std::process::exit(exit_status.code().unwrap_or(1)); 263 | } 264 | Some(existing_pid) => { 265 | proxy_existing_pid(existing_pid, &args)?; 266 | } 267 | } 268 | } 269 | 270 | Ok(()) 271 | } 272 | -------------------------------------------------------------------------------- /test/run_all_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Variables 6 | TEMP_DIR="" 7 | XRAY_PID="" 8 | CLOUDFLARE_DNS="1.1.1.1" 9 | 10 | # Function to install xray 11 | install_xray() { 12 | echo "Installing xray..." 13 | bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install 14 | echo "xray installation completed." 15 | } 16 | 17 | # Function to verify proxy dependency 18 | verify_proxy_dependency() { 19 | echo "Verifying proxy dependency..." 20 | 21 | # Try to make a request without xray running - should fail 22 | if sudo env RUST_LOG=debug cproxy --port 1082 --redirect-dns -- curl -s -I --connect-timeout 5 https://www.google.com > /dev/null 2>&1; then 23 | echo "ERROR: Request succeeded without proxy running! Test failed." 24 | exit 1 25 | else 26 | echo "Verified: Request failed without proxy as expected." 27 | fi 28 | } 29 | 30 | # Function to create xray configuration without TPROXY 31 | create_xray_config_normal() { 32 | echo "Creating xray configuration for normal mode..." 33 | 34 | cat > /tmp/xray_config_normal.json < /tmp/xray_config_tproxy.json < /dev/null 2>&1; then 108 | echo "Stopping xray with PID $XRAY_PID" 109 | sudo kill $XRAY_PID 110 | wait $XRAY_PID 2>/dev/null || true 111 | echo "xray stopped." 112 | fi 113 | } 114 | 115 | # Function to run cproxy test 116 | run_cproxy_test() { 117 | MODE=$1 118 | echo "Running cproxy test in mode: $MODE" 119 | 120 | if [ "$MODE" == "tproxy" ]; then 121 | CPROXY_MODE="--mode tproxy" 122 | else 123 | CPROXY_MODE="" 124 | fi 125 | 126 | # Example command to test proxying using curl 127 | echo "Executing curl through cproxy..." 128 | sudo env RUST_LOG=debug cproxy $CPROXY_MODE --port 1082 --redirect-dns -- curl -s -I https://www.google.com > /dev/null 129 | 130 | if [ $? -eq 0 ]; then 131 | echo "cproxy test in mode '$MODE': SUCCESS" 132 | else 133 | echo "cproxy test in mode '$MODE': FAILED" 134 | exit 1 135 | fi 136 | } 137 | 138 | # Function to run cproxy test with --cgroup-path option 139 | run_cproxy_cgroup_path_test() { 140 | echo "Running cproxy test with --cgroup-path option..." 141 | 142 | # Define cgroup path 143 | CGROUP_NAME="test_cproxy_cgroup_path" 144 | CGROUP_PATH="/sys/fs/cgroup/$CGROUP_NAME" 145 | 146 | # Create the cgroup directory (assuming cgroup v2) 147 | sudo mkdir -p $CGROUP_PATH 148 | 149 | # Define a cleanup function specific to this test 150 | cleanup_cgroup_path_test() { 151 | echo "Cleaning up cgroup path test..." 152 | if [ -n "${PROXY_PID:-}" ]; then 153 | sudo kill $PROXY_PID || true 154 | wait $PROXY_PID 2>/dev/null || true 155 | echo "cproxy process with PID $PROXY_PID terminated." 156 | fi 157 | sudo rmdir $CGROUP_PATH || true 158 | echo "Cgroup path $CGROUP_PATH removed." 159 | } 160 | 161 | # Trap to ensure cleanup happens 162 | trap cleanup_cgroup_path_test EXIT 163 | 164 | # Start cproxy with --cgroup-path 165 | sudo env RUST_LOG=debug cproxy --cgroup-path $CGROUP_PATH --port 1082 --redirect-dns & 166 | PROXY_PID=$! 167 | echo "cproxy started with PID $PROXY_PID for cgroup path test." 168 | sleep 2 # Wait for cproxy to initialize 169 | 170 | # Run curl within the specified cgroup 171 | echo "Running curl within cgroup $CGROUP_PATH..." 172 | sudo cgexec -g "*:$CGROUP_NAME" curl -s -I https://www.google.com > /dev/null 173 | 174 | if [ $? -eq 0 ]; then 175 | echo "cproxy --cgroup-path test: SUCCESS" 176 | else 177 | echo "cproxy --cgroup-path test: FAILED" 178 | sudo kill $PROXY_PID || true 179 | exit 1 180 | fi 181 | 182 | # Cleanup 183 | cleanup_cgroup_path_test 184 | 185 | # Remove the trap 186 | trap - EXIT 187 | } 188 | 189 | # Function to clean up in case of script exit 190 | cleanup() { 191 | echo "Cleaning up..." 192 | stop_xray 193 | } 194 | trap cleanup EXIT 195 | 196 | # Main Execution Flow 197 | main() { 198 | verify_proxy_dependency 199 | 200 | install_xray 201 | 202 | # Test without TPROXY 203 | create_xray_config_normal 204 | start_xray /tmp/xray_config_normal.json 205 | run_cproxy_test "normal" 206 | # Test with --cgroup-path option 207 | run_cproxy_cgroup_path_test 208 | stop_xray 209 | 210 | # Test with TPROXY 211 | create_xray_config_tproxy 212 | start_xray /tmp/xray_config_tproxy.json 213 | run_cproxy_test "tproxy" 214 | stop_xray 215 | 216 | echo "All end-to-end tests completed successfully!" 217 | } 218 | 219 | main 220 | --------------------------------------------------------------------------------