├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── default.nix ├── renovate.json └── src ├── main.rs ├── sctp.rs ├── tcp.rs └── udp.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Cargo build and test 2 | 3 | on: [ "push", "pull_request" ] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Check Formatting 15 | run: cargo fmt --all -- --check 16 | 17 | - name: Check Clippy 18 | run: cargo clippy --all 19 | 20 | build_and_test: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Build 26 | run: cargo build --verbose --all-features 27 | 28 | - name: Run tests 29 | run: cargo test --verbose 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz_release: 14 | name: Release-plz release 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Install Rust toolchain 22 | uses: dtolnay/rust-toolchain@stable 23 | - name: Run release-plz 24 | uses: MarcoIeni/release-plz-action@v0.5 25 | with: 26 | command: release 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 30 | 31 | release-plz_pr: 32 | name: Release-plz PR 33 | runs-on: ubuntu-latest 34 | concurrency: 35 | group: release-plz-${{ github.ref }} 36 | cancel-in-progress: false 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v4 40 | with: 41 | fetch-depth: 0 42 | - name: Install Rust toolchain 43 | uses: dtolnay/rust-toolchain@stable 44 | - name: Run release-plz 45 | uses: MarcoIeni/release-plz-action@v0.5 46 | with: 47 | command: release-pr 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /*/target 3 | **/*.rs.bk 4 | 5 | /.cargo 6 | 7 | /.idea 8 | /*.iml 9 | 10 | /.envrc 11 | -------------------------------------------------------------------------------- /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 = "anstream" 22 | version = "0.6.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "is_terminal_polyfill", 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle" 37 | version = "1.0.10" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 40 | 41 | [[package]] 42 | name = "anstyle-parse" 43 | version = "0.2.6" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 46 | dependencies = [ 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-query" 52 | version = "1.1.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 55 | dependencies = [ 56 | "windows-sys 0.59.0", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-wincon" 61 | version = "3.0.7" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 64 | dependencies = [ 65 | "anstyle", 66 | "once_cell", 67 | "windows-sys 0.59.0", 68 | ] 69 | 70 | [[package]] 71 | name = "anyhow" 72 | version = "1.0.98" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 75 | 76 | [[package]] 77 | name = "async-trait" 78 | version = "0.1.88" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 81 | dependencies = [ 82 | "proc-macro2", 83 | "quote", 84 | "syn", 85 | ] 86 | 87 | [[package]] 88 | name = "autocfg" 89 | version = "1.4.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 92 | 93 | [[package]] 94 | name = "backtrace" 95 | version = "0.3.75" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 98 | dependencies = [ 99 | "addr2line", 100 | "cfg-if", 101 | "libc", 102 | "miniz_oxide", 103 | "object", 104 | "rustc-demangle", 105 | "windows-targets", 106 | ] 107 | 108 | [[package]] 109 | name = "bitflags" 110 | version = "2.9.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 113 | 114 | [[package]] 115 | name = "bytes" 116 | version = "1.10.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 119 | 120 | [[package]] 121 | name = "cfg-if" 122 | version = "1.0.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 125 | 126 | [[package]] 127 | name = "cfg_aliases" 128 | version = "0.2.1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 131 | 132 | [[package]] 133 | name = "clap" 134 | version = "4.5.40" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" 137 | dependencies = [ 138 | "clap_builder", 139 | "clap_derive", 140 | ] 141 | 142 | [[package]] 143 | name = "clap_builder" 144 | version = "4.5.40" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" 147 | dependencies = [ 148 | "anstream", 149 | "anstyle", 150 | "clap_lex", 151 | "strsim", 152 | ] 153 | 154 | [[package]] 155 | name = "clap_derive" 156 | version = "4.5.40" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" 159 | dependencies = [ 160 | "heck", 161 | "proc-macro2", 162 | "quote", 163 | "syn", 164 | ] 165 | 166 | [[package]] 167 | name = "clap_lex" 168 | version = "0.7.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 171 | 172 | [[package]] 173 | name = "colorchoice" 174 | version = "1.0.3" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 177 | 178 | [[package]] 179 | name = "gimli" 180 | version = "0.31.1" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 183 | 184 | [[package]] 185 | name = "heck" 186 | version = "0.5.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 189 | 190 | [[package]] 191 | name = "is_terminal_polyfill" 192 | version = "1.70.1" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 195 | 196 | [[package]] 197 | name = "lazy_static" 198 | version = "1.5.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 201 | 202 | [[package]] 203 | name = "libc" 204 | version = "0.2.172" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 207 | 208 | [[package]] 209 | name = "lock_api" 210 | version = "0.4.12" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 213 | dependencies = [ 214 | "autocfg", 215 | "scopeguard", 216 | ] 217 | 218 | [[package]] 219 | name = "log" 220 | version = "0.4.27" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 223 | 224 | [[package]] 225 | name = "memchr" 226 | version = "2.7.4" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 229 | 230 | [[package]] 231 | name = "miniz_oxide" 232 | version = "0.8.8" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 235 | dependencies = [ 236 | "adler2", 237 | ] 238 | 239 | [[package]] 240 | name = "mio" 241 | version = "1.0.3" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 244 | dependencies = [ 245 | "libc", 246 | "wasi", 247 | "windows-sys 0.52.0", 248 | ] 249 | 250 | [[package]] 251 | name = "netns-proxy" 252 | version = "0.2.0" 253 | dependencies = [ 254 | "anyhow", 255 | "async-trait", 256 | "clap", 257 | "nix", 258 | "tokio", 259 | "tracing", 260 | "tracing-subscriber", 261 | ] 262 | 263 | [[package]] 264 | name = "nix" 265 | version = "0.30.1" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 268 | dependencies = [ 269 | "bitflags", 270 | "cfg-if", 271 | "cfg_aliases", 272 | "libc", 273 | ] 274 | 275 | [[package]] 276 | name = "nu-ansi-term" 277 | version = "0.46.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 280 | dependencies = [ 281 | "overload", 282 | "winapi", 283 | ] 284 | 285 | [[package]] 286 | name = "object" 287 | version = "0.36.7" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 290 | dependencies = [ 291 | "memchr", 292 | ] 293 | 294 | [[package]] 295 | name = "once_cell" 296 | version = "1.21.3" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 299 | 300 | [[package]] 301 | name = "overload" 302 | version = "0.1.1" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 305 | 306 | [[package]] 307 | name = "parking_lot" 308 | version = "0.12.3" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 311 | dependencies = [ 312 | "lock_api", 313 | "parking_lot_core", 314 | ] 315 | 316 | [[package]] 317 | name = "parking_lot_core" 318 | version = "0.9.10" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 321 | dependencies = [ 322 | "cfg-if", 323 | "libc", 324 | "redox_syscall", 325 | "smallvec", 326 | "windows-targets", 327 | ] 328 | 329 | [[package]] 330 | name = "pin-project-lite" 331 | version = "0.2.16" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 334 | 335 | [[package]] 336 | name = "proc-macro2" 337 | version = "1.0.95" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 340 | dependencies = [ 341 | "unicode-ident", 342 | ] 343 | 344 | [[package]] 345 | name = "quote" 346 | version = "1.0.40" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 349 | dependencies = [ 350 | "proc-macro2", 351 | ] 352 | 353 | [[package]] 354 | name = "redox_syscall" 355 | version = "0.5.12" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 358 | dependencies = [ 359 | "bitflags", 360 | ] 361 | 362 | [[package]] 363 | name = "rustc-demangle" 364 | version = "0.1.24" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 367 | 368 | [[package]] 369 | name = "scopeguard" 370 | version = "1.2.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 373 | 374 | [[package]] 375 | name = "sharded-slab" 376 | version = "0.1.7" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 379 | dependencies = [ 380 | "lazy_static", 381 | ] 382 | 383 | [[package]] 384 | name = "signal-hook-registry" 385 | version = "1.4.5" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 388 | dependencies = [ 389 | "libc", 390 | ] 391 | 392 | [[package]] 393 | name = "smallvec" 394 | version = "1.15.0" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 397 | 398 | [[package]] 399 | name = "socket2" 400 | version = "0.5.9" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" 403 | dependencies = [ 404 | "libc", 405 | "windows-sys 0.52.0", 406 | ] 407 | 408 | [[package]] 409 | name = "strsim" 410 | version = "0.11.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 413 | 414 | [[package]] 415 | name = "syn" 416 | version = "2.0.101" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 419 | dependencies = [ 420 | "proc-macro2", 421 | "quote", 422 | "unicode-ident", 423 | ] 424 | 425 | [[package]] 426 | name = "thread_local" 427 | version = "1.1.8" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 430 | dependencies = [ 431 | "cfg-if", 432 | "once_cell", 433 | ] 434 | 435 | [[package]] 436 | name = "tokio" 437 | version = "1.45.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" 440 | dependencies = [ 441 | "backtrace", 442 | "bytes", 443 | "libc", 444 | "mio", 445 | "parking_lot", 446 | "pin-project-lite", 447 | "signal-hook-registry", 448 | "socket2", 449 | "tokio-macros", 450 | "windows-sys 0.52.0", 451 | ] 452 | 453 | [[package]] 454 | name = "tokio-macros" 455 | version = "2.5.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 458 | dependencies = [ 459 | "proc-macro2", 460 | "quote", 461 | "syn", 462 | ] 463 | 464 | [[package]] 465 | name = "tracing" 466 | version = "0.1.41" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 469 | dependencies = [ 470 | "pin-project-lite", 471 | "tracing-attributes", 472 | "tracing-core", 473 | ] 474 | 475 | [[package]] 476 | name = "tracing-attributes" 477 | version = "0.1.28" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 480 | dependencies = [ 481 | "proc-macro2", 482 | "quote", 483 | "syn", 484 | ] 485 | 486 | [[package]] 487 | name = "tracing-core" 488 | version = "0.1.33" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 491 | dependencies = [ 492 | "once_cell", 493 | "valuable", 494 | ] 495 | 496 | [[package]] 497 | name = "tracing-log" 498 | version = "0.2.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 501 | dependencies = [ 502 | "log", 503 | "once_cell", 504 | "tracing-core", 505 | ] 506 | 507 | [[package]] 508 | name = "tracing-subscriber" 509 | version = "0.3.19" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 512 | dependencies = [ 513 | "nu-ansi-term", 514 | "sharded-slab", 515 | "smallvec", 516 | "thread_local", 517 | "tracing-core", 518 | "tracing-log", 519 | ] 520 | 521 | [[package]] 522 | name = "unicode-ident" 523 | version = "1.0.18" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 526 | 527 | [[package]] 528 | name = "utf8parse" 529 | version = "0.2.2" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 532 | 533 | [[package]] 534 | name = "valuable" 535 | version = "0.1.1" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 538 | 539 | [[package]] 540 | name = "wasi" 541 | version = "0.11.0+wasi-snapshot-preview1" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 544 | 545 | [[package]] 546 | name = "winapi" 547 | version = "0.3.9" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 550 | dependencies = [ 551 | "winapi-i686-pc-windows-gnu", 552 | "winapi-x86_64-pc-windows-gnu", 553 | ] 554 | 555 | [[package]] 556 | name = "winapi-i686-pc-windows-gnu" 557 | version = "0.4.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 560 | 561 | [[package]] 562 | name = "winapi-x86_64-pc-windows-gnu" 563 | version = "0.4.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 566 | 567 | [[package]] 568 | name = "windows-sys" 569 | version = "0.52.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 572 | dependencies = [ 573 | "windows-targets", 574 | ] 575 | 576 | [[package]] 577 | name = "windows-sys" 578 | version = "0.59.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 581 | dependencies = [ 582 | "windows-targets", 583 | ] 584 | 585 | [[package]] 586 | name = "windows-targets" 587 | version = "0.52.6" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 590 | dependencies = [ 591 | "windows_aarch64_gnullvm", 592 | "windows_aarch64_msvc", 593 | "windows_i686_gnu", 594 | "windows_i686_gnullvm", 595 | "windows_i686_msvc", 596 | "windows_x86_64_gnu", 597 | "windows_x86_64_gnullvm", 598 | "windows_x86_64_msvc", 599 | ] 600 | 601 | [[package]] 602 | name = "windows_aarch64_gnullvm" 603 | version = "0.52.6" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 606 | 607 | [[package]] 608 | name = "windows_aarch64_msvc" 609 | version = "0.52.6" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 612 | 613 | [[package]] 614 | name = "windows_i686_gnu" 615 | version = "0.52.6" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 618 | 619 | [[package]] 620 | name = "windows_i686_gnullvm" 621 | version = "0.52.6" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 624 | 625 | [[package]] 626 | name = "windows_i686_msvc" 627 | version = "0.52.6" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 630 | 631 | [[package]] 632 | name = "windows_x86_64_gnu" 633 | version = "0.52.6" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 636 | 637 | [[package]] 638 | name = "windows_x86_64_gnullvm" 639 | version = "0.52.6" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 642 | 643 | [[package]] 644 | name = "windows_x86_64_msvc" 645 | version = "0.52.6" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 648 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netns-proxy" 3 | version = "0.2.0" 4 | edition = "2021" 5 | description = "Forwards incoming requests to a given target while outgoing connections are created from different namespace" 6 | authors = [ 7 | "Dustin Frisch ", 8 | ] 9 | 10 | license = "MIT" 11 | 12 | repository = "https://github.com/fooker/netns-proxy" 13 | readme = "./README.md" 14 | 15 | keywords = [ 16 | "linux", 17 | "netns", 18 | "proxy", 19 | "namespace", 20 | ] 21 | 22 | [dependencies] 23 | anyhow = "1.0.98" 24 | clap = { version = "4.5.38", features = ["derive"] } 25 | nix = { version = "0.30.1", features = ["user", "sched", "fs"] } 26 | tokio = { version = "1.45.0", features = ["full"] } 27 | async-trait = "0.1.88" 28 | tracing = "0.1.41" 29 | tracing-subscriber = "0.3.19" 30 | 31 | [lints.rust] 32 | bad_style = "deny" 33 | dead_code = "deny" 34 | improper_ctypes = "deny" 35 | non_shorthand_field_patterns = "deny" 36 | no_mangle_generic_items = "deny" 37 | overflowing_literals = "deny" 38 | path_statements = "deny" 39 | patterns_in_fns_without_body = "deny" 40 | trivial_casts = "deny" 41 | trivial_numeric_casts = "deny" 42 | unconditional_recursion = "deny" 43 | unused = "deny" 44 | unused_allocation = "deny" 45 | unused_comparisons = "deny" 46 | unused_extern_crates = "deny" 47 | unused_import_braces = "deny" 48 | unused_parens = "deny" 49 | unused_qualifications = "deny" 50 | while_true = "deny" 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Dustin Frisch 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netns-proxy 2 | 3 | A simple and slim proxy to forward ports from and into linux network 4 | namespaces. 5 | 6 | 7 | ## Overview 8 | 9 | `netns-proxy` is a plain and simple proxy for TCP, UDP and SCTP that allows to 10 | accept connections in one network namespace and forwards them into another. 11 | 12 | This allows to expose ports that are only accessible in one namespace to a 13 | second one. This provides a lightweight alternative to connecting namespace with 14 | `veth` interfaces which require address management and firewall rules for 15 | forwarding. 16 | 17 | `netns-proxy` does its job by opening a socket when it is started and switches 18 | to the target namespace before opening connections to the exposed server. This 19 | allows the accepting socket to exist in the original namespace while forwarded 20 | connection happen in the specified namespace. 21 | 22 | 23 | ## Usage 24 | 25 | ``` 26 | netns-proxy 27 | ``` 28 | 29 | `` is the name of the network namespace from which forwarded connections 30 | are created. 31 | 32 | `` the target host and port to forward connections to. 33 | 34 | See `netns-proxy --help` for full details and options. 35 | 36 | 37 | ## Getting Help 38 | 39 | Feel free to open an [Issue](https://github.com/fooker/netns-proxy/issues) or 40 | write me a [Mail](mailto:fooker@lab.sh). 41 | 42 | 43 | ## Contributing 44 | 45 | :+1: Thanks for your help to improve the project! Pleas don't hesitate to 46 | create an [Issue](https://github.com/fooker/netns-proxy/issues) if you find 47 | something is off or even consider creating a patch and propose a Pull-Request. 48 | 49 | 50 | ## License 51 | 52 | The project is licensed under the [MIT license](./LICENSE). 53 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { rustPlatform, lib, ... }: 2 | 3 | rustPlatform.buildRustPackage rec { 4 | name = "netns-proxy"; 5 | 6 | src = lib.cleanSource ./.; 7 | 8 | cargoLock = { 9 | lockFile = ./Cargo.lock; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":automergeMinor", 6 | ":semanticCommitTypeAll(chore)" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::fmt::Debug; 3 | use std::net::{Ipv6Addr, SocketAddr}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use anyhow::{Context, Result}; 7 | use async_trait::async_trait; 8 | use clap::{Parser, ValueEnum}; 9 | use nix::sched::{setns, CloneFlags}; 10 | use nix::unistd::{setgid, setuid, Gid, Uid}; 11 | use tracing::{debug, info, trace, Level}; 12 | 13 | mod sctp; 14 | mod tcp; 15 | mod udp; 16 | 17 | #[async_trait] 18 | pub trait Proxy: Debug { 19 | async fn listen(bind: SocketAddr) -> Result 20 | where 21 | Self: Sized; 22 | async fn run(self: Box, target: SocketAddr) -> Result<()>; 23 | } 24 | 25 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] 26 | #[allow(clippy::upper_case_acronyms)] 27 | enum Proto { 28 | TCP, 29 | UDP, 30 | SCTP, 31 | } 32 | 33 | impl fmt::Display for Proto { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | f.write_str(match self { 36 | Proto::TCP => "tcp", 37 | Proto::UDP => "udp", 38 | Proto::SCTP => "sctp", 39 | }) 40 | } 41 | } 42 | 43 | #[derive(Parser, Debug)] 44 | #[clap(author, version, about, long_about = None)] 45 | #[deny(missing_docs)] 46 | struct Opts { 47 | /// The namespace to open connections from. Can be namespace name or full path 48 | netns: PathBuf, 49 | 50 | /// The network protocol to use 51 | #[clap(short, long, value_enum, default_value = "tcp")] 52 | proto: Proto, 53 | 54 | /// Target to forward requests to 55 | target: SocketAddr, 56 | 57 | /// Listen on incoming requests 58 | #[clap(short, long)] 59 | bind: Option, 60 | 61 | /// Verbose mode 62 | #[clap(short, long, action = clap::ArgAction::Count)] 63 | verbose: u8, 64 | } 65 | 66 | #[tokio::main] 67 | async fn main() -> Result<()> { 68 | let opts: Opts = Opts::parse(); 69 | 70 | tracing_subscriber::fmt() 71 | .with_max_level(match opts.verbose { 72 | 0 => Level::WARN, 73 | 1 => Level::INFO, 74 | 2 => Level::DEBUG, 75 | 3.. => Level::TRACE, 76 | }) 77 | .init(); 78 | 79 | // Open listening socket for proxy in current namespace 80 | let bind = opts 81 | .bind 82 | .unwrap_or_else(|| SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), opts.target.port())); 83 | info!("Listening on {}:{}", opts.proto, bind); 84 | 85 | let proxy: Box = match opts.proto { 86 | Proto::TCP => Box::new(tcp::Proxy::listen(bind).await?), 87 | Proto::UDP => Box::new(udp::Proxy::listen(bind).await?), 88 | Proto::SCTP => Box::new(sctp::Proxy::listen(bind).await?), 89 | }; 90 | trace!("Proxy created: {:?}", proxy); 91 | 92 | // Open network namespace path and switch over 93 | let netns = Path::new("/var/run/netns").join(opts.netns); 94 | debug!("Using netns: {}", netns.display()); 95 | 96 | let netns = tokio::fs::File::open(&netns) 97 | .await 98 | .with_context(|| format!("Could not open network namespace file: {netns:?}"))?; 99 | 100 | setns(netns, CloneFlags::CLONE_NEWNET).context("Switching network namespace failed")?; 101 | trace!("Network namespace switched"); 102 | 103 | // Dropping privileges 104 | setgid(Gid::current()).context("Failed to drop group privileges")?; 105 | trace!("Group privileges dropped"); 106 | setuid(Uid::current()).context("Failed to drop user privileges")?; 107 | trace!("User privileges dropped"); 108 | 109 | // Start forwarding incoming connections 110 | proxy.run(opts.target).await?; 111 | 112 | return Ok(()); 113 | } 114 | -------------------------------------------------------------------------------- /src/sctp.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use anyhow::Result; 4 | use async_trait::async_trait; 5 | 6 | #[derive(Debug)] 7 | pub struct Proxy {} 8 | 9 | #[async_trait] 10 | impl super::Proxy for Proxy { 11 | async fn listen(_bind: SocketAddr) -> Result { 12 | unimplemented!() 13 | } 14 | 15 | async fn run(mut self: Box, _target: SocketAddr) -> Result<()> { 16 | todo!() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/tcp.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use anyhow::{Context, Result}; 4 | use async_trait::async_trait; 5 | use tokio::net::{TcpListener, TcpStream}; 6 | use tracing::{debug, span, trace, warn, Level}; 7 | 8 | #[derive(Debug)] 9 | pub struct Proxy { 10 | socket: TcpListener, 11 | } 12 | 13 | #[async_trait] 14 | impl super::Proxy for Proxy { 15 | async fn listen(bind: SocketAddr) -> Result { 16 | let socket = TcpListener::bind(bind) 17 | .await 18 | .with_context(|| format!("Failed to bind to TCP socket: {bind}"))?; 19 | debug!("Created TCP socket"); 20 | 21 | return Ok(Self { socket }); 22 | } 23 | 24 | async fn run(mut self: Box, target: SocketAddr) -> Result<()> { 25 | loop { 26 | let result: Result<()> = async { 27 | trace!("Awaiting new connection"); 28 | 29 | let (mut client, remote) = self 30 | .socket 31 | .accept() 32 | .await 33 | .context("Failed to accept connection")?; 34 | debug!("New client connected: {}", remote); 35 | 36 | let _ = span!(Level::DEBUG, "Client connection", connection = ?remote).entered(); 37 | 38 | let mut upstream = TcpStream::connect(target) 39 | .await 40 | .with_context(|| format!("Failed to connect to target: {target}"))?; 41 | trace!("Upstream connection established"); 42 | 43 | tokio::spawn(async move { 44 | match tokio::io::copy_bidirectional(&mut client, &mut upstream).await { 45 | Ok(_) => { 46 | trace!("Upstream connection closed"); 47 | } 48 | Err(err) => { 49 | warn!("Upstream connection failed: {}", err); 50 | } 51 | } 52 | }); 53 | 54 | Ok(()) 55 | } 56 | .await; 57 | 58 | if let Err(err) = result { 59 | eprintln!("Error: {err:?}"); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/udp.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::HashMap; 3 | use std::net::{Ipv6Addr, SocketAddr}; 4 | use std::sync::Arc; 5 | use std::time::Duration; 6 | 7 | use anyhow::{bail, Context, Result}; 8 | use async_trait::async_trait; 9 | use tokio::net::UdpSocket; 10 | use tokio::sync::Mutex; 11 | use tracing::{debug, error, span, trace, Level}; 12 | 13 | const BUFFER_SIZE: usize = 64 * 1024; 14 | const TIMEOUT: Duration = Duration::from_secs(120); 15 | 16 | #[derive(Debug)] 17 | pub struct Proxy { 18 | socket: Arc, 19 | } 20 | 21 | #[async_trait] 22 | impl super::Proxy for Proxy { 23 | async fn listen(bind: SocketAddr) -> Result { 24 | let socket = Arc::new(UdpSocket::bind(bind).await?); 25 | debug!("Created UDP socket"); 26 | 27 | return Ok(Self { socket }); 28 | } 29 | 30 | async fn run(mut self: Box, target: SocketAddr) -> Result<()> { 31 | let mut buffer = [0u8; BUFFER_SIZE]; 32 | 33 | let conn_track: Arc>>> = Default::default(); 34 | 35 | loop { 36 | trace!("Waiting for next packet"); 37 | 38 | let (size, remote) = self.socket.recv_from(&mut buffer).await?; 39 | let buffer = &buffer[0..size]; 40 | trace!("Received UDP packet from client: {} bytes", size); 41 | 42 | let _ = span!(Level::DEBUG, "Session", remote = ?remote).entered(); 43 | 44 | let upstream = { 45 | match conn_track.lock().await.entry(remote) { 46 | Entry::Vacant(entry) => { 47 | debug!("New UDP session: {}", remote); 48 | 49 | let upstream = Arc::new( 50 | UdpSocket::bind(SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0)) 51 | .await 52 | .context("Failed to bind upstream socket")?, 53 | ); 54 | upstream.connect(target).await?; 55 | trace!("Upstream socket created: {}", upstream.local_addr()?); 56 | 57 | { 58 | let conn_track = conn_track.clone(); 59 | let upstream = upstream.clone(); 60 | let socket = self.socket.clone(); 61 | 62 | tokio::spawn(async move { 63 | let mut buffer = [0u8; BUFFER_SIZE]; 64 | 65 | loop { 66 | trace!("Waiting for next packet"); 67 | 68 | match tokio::time::timeout(TIMEOUT, upstream.recv(&mut buffer)) 69 | .await 70 | { 71 | Ok(Ok(size)) => { 72 | trace!("Packet received from upstream: {}", size); 73 | trace!("Responding to: {}", remote); 74 | 75 | socket 76 | .send_to(&buffer[0..size], &remote) 77 | .await 78 | .context("Failed to send response")?; 79 | } 80 | 81 | Ok(Err(err)) => { 82 | error!("Failed to receive from upstream: {}", err); 83 | bail!(err); 84 | } 85 | 86 | Err(_) => { 87 | trace!("UDP session timeout"); 88 | 89 | conn_track.lock().await.remove(&remote); 90 | 91 | break; 92 | } 93 | } 94 | } 95 | 96 | Ok::<(), anyhow::Error>(()) 97 | }); 98 | } 99 | 100 | entry.insert(upstream).clone() 101 | } 102 | 103 | Entry::Occupied(entry) => { 104 | trace!("Reuse existing socket"); 105 | entry.into_mut().clone() 106 | } 107 | } 108 | }; 109 | 110 | upstream.send(buffer).await?; 111 | } 112 | } 113 | } 114 | --------------------------------------------------------------------------------