├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── cli ├── Cargo.lock ├── Cargo.toml └── src │ ├── bin │ ├── client.rs │ └── server.rs │ └── lib.rs ├── img └── session.drawio.png ├── rust-toolchain.toml ├── src ├── lib.rs ├── listen.rs ├── message.rs ├── receiver.rs ├── recv_buf.rs ├── send_buf.rs ├── sender.rs └── stream.rs └── tests └── bench.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "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 = "anstream" 22 | version = "0.6.4" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "utf8parse", 32 | ] 33 | 34 | [[package]] 35 | name = "anstyle" 36 | version = "1.0.4" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" 39 | 40 | [[package]] 41 | name = "anstyle-parse" 42 | version = "0.2.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" 45 | dependencies = [ 46 | "utf8parse", 47 | ] 48 | 49 | [[package]] 50 | name = "anstyle-query" 51 | version = "1.0.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 54 | dependencies = [ 55 | "windows-sys", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle-wincon" 60 | version = "3.0.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" 63 | dependencies = [ 64 | "anstyle", 65 | "windows-sys", 66 | ] 67 | 68 | [[package]] 69 | name = "anyhow" 70 | version = "1.0.86" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 73 | 74 | [[package]] 75 | name = "async_async_io" 76 | version = "0.2.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "28eee4e3d382de2cff2b8ada55acce932ab849bbe6d480517b0111ddeb84a4cc" 79 | dependencies = [ 80 | "futures-core", 81 | "reusable-box-future", 82 | "tokio", 83 | ] 84 | 85 | [[package]] 86 | name = "autocfg" 87 | version = "1.1.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 90 | 91 | [[package]] 92 | name = "backtrace" 93 | version = "0.3.69" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 96 | dependencies = [ 97 | "addr2line", 98 | "cc", 99 | "cfg-if", 100 | "libc", 101 | "miniz_oxide", 102 | "object", 103 | "rustc-demangle", 104 | ] 105 | 106 | [[package]] 107 | name = "bitflags" 108 | version = "1.3.2" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 111 | 112 | [[package]] 113 | name = "bytes" 114 | version = "1.5.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 117 | 118 | [[package]] 119 | name = "cc" 120 | version = "1.0.83" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 123 | dependencies = [ 124 | "libc", 125 | ] 126 | 127 | [[package]] 128 | name = "cfg-if" 129 | version = "1.0.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 132 | 133 | [[package]] 134 | name = "clap" 135 | version = "4.4.6" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" 138 | dependencies = [ 139 | "clap_builder", 140 | "clap_derive", 141 | ] 142 | 143 | [[package]] 144 | name = "clap_builder" 145 | version = "4.4.6" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" 148 | dependencies = [ 149 | "anstream", 150 | "anstyle", 151 | "clap_lex", 152 | "strsim", 153 | ] 154 | 155 | [[package]] 156 | name = "clap_derive" 157 | version = "4.4.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" 160 | dependencies = [ 161 | "heck", 162 | "proc-macro2", 163 | "quote", 164 | "syn", 165 | ] 166 | 167 | [[package]] 168 | name = "clap_lex" 169 | version = "0.5.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" 172 | 173 | [[package]] 174 | name = "cli" 175 | version = "0.1.0" 176 | dependencies = [ 177 | "anyhow", 178 | "bytes", 179 | "clap", 180 | "mptcp", 181 | "serde", 182 | "serde_json", 183 | "tokio", 184 | ] 185 | 186 | [[package]] 187 | name = "colorchoice" 188 | version = "1.0.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 191 | 192 | [[package]] 193 | name = "futures-core" 194 | version = "0.3.28" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 197 | 198 | [[package]] 199 | name = "getrandom" 200 | version = "0.2.10" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 203 | dependencies = [ 204 | "cfg-if", 205 | "libc", 206 | "wasi", 207 | ] 208 | 209 | [[package]] 210 | name = "gimli" 211 | version = "0.28.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 214 | 215 | [[package]] 216 | name = "heck" 217 | version = "0.4.1" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 220 | 221 | [[package]] 222 | name = "hermit-abi" 223 | version = "0.3.3" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 226 | 227 | [[package]] 228 | name = "itoa" 229 | version = "1.0.11" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 232 | 233 | [[package]] 234 | name = "libc" 235 | version = "0.2.149" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" 238 | 239 | [[package]] 240 | name = "lock_api" 241 | version = "0.4.11" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 244 | dependencies = [ 245 | "autocfg", 246 | "scopeguard", 247 | ] 248 | 249 | [[package]] 250 | name = "memchr" 251 | version = "2.6.4" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 254 | 255 | [[package]] 256 | name = "miniz_oxide" 257 | version = "0.7.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 260 | dependencies = [ 261 | "adler", 262 | ] 263 | 264 | [[package]] 265 | name = "mio" 266 | version = "0.8.8" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 269 | dependencies = [ 270 | "libc", 271 | "wasi", 272 | "windows-sys", 273 | ] 274 | 275 | [[package]] 276 | name = "mptcp" 277 | version = "0.1.0" 278 | dependencies = [ 279 | "anyhow", 280 | "async_async_io", 281 | "bytes", 282 | "rand", 283 | "scopeguard", 284 | "thiserror", 285 | "tokio", 286 | ] 287 | 288 | [[package]] 289 | name = "num_cpus" 290 | version = "1.16.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 293 | dependencies = [ 294 | "hermit-abi", 295 | "libc", 296 | ] 297 | 298 | [[package]] 299 | name = "object" 300 | version = "0.32.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 303 | dependencies = [ 304 | "memchr", 305 | ] 306 | 307 | [[package]] 308 | name = "parking_lot" 309 | version = "0.12.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 312 | dependencies = [ 313 | "lock_api", 314 | "parking_lot_core", 315 | ] 316 | 317 | [[package]] 318 | name = "parking_lot_core" 319 | version = "0.9.9" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 322 | dependencies = [ 323 | "cfg-if", 324 | "libc", 325 | "redox_syscall", 326 | "smallvec", 327 | "windows-targets", 328 | ] 329 | 330 | [[package]] 331 | name = "pin-project-lite" 332 | version = "0.2.13" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 335 | 336 | [[package]] 337 | name = "ppv-lite86" 338 | version = "0.2.17" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 341 | 342 | [[package]] 343 | name = "proc-macro2" 344 | version = "1.0.86" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 347 | dependencies = [ 348 | "unicode-ident", 349 | ] 350 | 351 | [[package]] 352 | name = "quote" 353 | version = "1.0.37" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 356 | dependencies = [ 357 | "proc-macro2", 358 | ] 359 | 360 | [[package]] 361 | name = "rand" 362 | version = "0.8.5" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 365 | dependencies = [ 366 | "libc", 367 | "rand_chacha", 368 | "rand_core", 369 | ] 370 | 371 | [[package]] 372 | name = "rand_chacha" 373 | version = "0.3.1" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 376 | dependencies = [ 377 | "ppv-lite86", 378 | "rand_core", 379 | ] 380 | 381 | [[package]] 382 | name = "rand_core" 383 | version = "0.6.4" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 386 | dependencies = [ 387 | "getrandom", 388 | ] 389 | 390 | [[package]] 391 | name = "redox_syscall" 392 | version = "0.4.1" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 395 | dependencies = [ 396 | "bitflags", 397 | ] 398 | 399 | [[package]] 400 | name = "reusable-box-future" 401 | version = "0.2.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "1e0e61cd21fbddd85fbd9367b775660a01d388c08a61c6d2824af480b0309bb9" 404 | 405 | [[package]] 406 | name = "rustc-demangle" 407 | version = "0.1.23" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 410 | 411 | [[package]] 412 | name = "ryu" 413 | version = "1.0.18" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 416 | 417 | [[package]] 418 | name = "scopeguard" 419 | version = "1.2.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 422 | 423 | [[package]] 424 | name = "serde" 425 | version = "1.0.209" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" 428 | dependencies = [ 429 | "serde_derive", 430 | ] 431 | 432 | [[package]] 433 | name = "serde_derive" 434 | version = "1.0.209" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" 437 | dependencies = [ 438 | "proc-macro2", 439 | "quote", 440 | "syn", 441 | ] 442 | 443 | [[package]] 444 | name = "serde_json" 445 | version = "1.0.127" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" 448 | dependencies = [ 449 | "itoa", 450 | "memchr", 451 | "ryu", 452 | "serde", 453 | ] 454 | 455 | [[package]] 456 | name = "signal-hook-registry" 457 | version = "1.4.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 460 | dependencies = [ 461 | "libc", 462 | ] 463 | 464 | [[package]] 465 | name = "smallvec" 466 | version = "1.11.1" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" 469 | 470 | [[package]] 471 | name = "socket2" 472 | version = "0.5.5" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 475 | dependencies = [ 476 | "libc", 477 | "windows-sys", 478 | ] 479 | 480 | [[package]] 481 | name = "strsim" 482 | version = "0.10.0" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 485 | 486 | [[package]] 487 | name = "syn" 488 | version = "2.0.77" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 491 | dependencies = [ 492 | "proc-macro2", 493 | "quote", 494 | "unicode-ident", 495 | ] 496 | 497 | [[package]] 498 | name = "thiserror" 499 | version = "1.0.50" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 502 | dependencies = [ 503 | "thiserror-impl", 504 | ] 505 | 506 | [[package]] 507 | name = "thiserror-impl" 508 | version = "1.0.50" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 511 | dependencies = [ 512 | "proc-macro2", 513 | "quote", 514 | "syn", 515 | ] 516 | 517 | [[package]] 518 | name = "tokio" 519 | version = "1.33.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" 522 | dependencies = [ 523 | "backtrace", 524 | "bytes", 525 | "libc", 526 | "mio", 527 | "num_cpus", 528 | "parking_lot", 529 | "pin-project-lite", 530 | "signal-hook-registry", 531 | "socket2", 532 | "tokio-macros", 533 | "windows-sys", 534 | ] 535 | 536 | [[package]] 537 | name = "tokio-macros" 538 | version = "2.1.0" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 541 | dependencies = [ 542 | "proc-macro2", 543 | "quote", 544 | "syn", 545 | ] 546 | 547 | [[package]] 548 | name = "unicode-ident" 549 | version = "1.0.12" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 552 | 553 | [[package]] 554 | name = "utf8parse" 555 | version = "0.2.1" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 558 | 559 | [[package]] 560 | name = "wasi" 561 | version = "0.11.0+wasi-snapshot-preview1" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 564 | 565 | [[package]] 566 | name = "windows-sys" 567 | version = "0.48.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 570 | dependencies = [ 571 | "windows-targets", 572 | ] 573 | 574 | [[package]] 575 | name = "windows-targets" 576 | version = "0.48.5" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 579 | dependencies = [ 580 | "windows_aarch64_gnullvm", 581 | "windows_aarch64_msvc", 582 | "windows_i686_gnu", 583 | "windows_i686_msvc", 584 | "windows_x86_64_gnu", 585 | "windows_x86_64_gnullvm", 586 | "windows_x86_64_msvc", 587 | ] 588 | 589 | [[package]] 590 | name = "windows_aarch64_gnullvm" 591 | version = "0.48.5" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 594 | 595 | [[package]] 596 | name = "windows_aarch64_msvc" 597 | version = "0.48.5" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 600 | 601 | [[package]] 602 | name = "windows_i686_gnu" 603 | version = "0.48.5" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 606 | 607 | [[package]] 608 | name = "windows_i686_msvc" 609 | version = "0.48.5" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 612 | 613 | [[package]] 614 | name = "windows_x86_64_gnu" 615 | version = "0.48.5" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 618 | 619 | [[package]] 620 | name = "windows_x86_64_gnullvm" 621 | version = "0.48.5" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 624 | 625 | [[package]] 626 | name = "windows_x86_64_msvc" 627 | version = "0.48.5" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 630 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mptcp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [workspace] 9 | members = ["cli"] 10 | 11 | [dependencies] 12 | anyhow = "1.0.86" 13 | async_async_io = "0.2" 14 | bytes = "1" 15 | rand = "0.8" 16 | scopeguard = "1" 17 | thiserror = "1" 18 | tokio = { version = "1", features = ["io-util", "rt", "macros", "sync", "time"] } 19 | 20 | [dev-dependencies] 21 | tokio = { version = "1", features = ["full"] } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi-path TCP 2 | 3 | ![Session](img/session.drawio.png) 4 | 5 | ## How to use 6 | 7 | 1. Install Rust: 8 | 1. `git clone .git` 9 | 1. On the server side, run `cargo run -r -p cli --bin server -- config.json` 10 | 1. On the client side, run `cargo run -r -p cli --bin client -- config.json` 11 | 12 | ### Client-side `config.json` 13 | 14 | - **servers**: The server endpoints to connect to. 15 | - **local_bind**: The local port to bind to for client access. 16 | 17 | ```json 18 | { 19 | "servers": ["1.2.3.4:30001", "1.2.3.4:30001"], 20 | "local_bind": "0.0.0.0:12811" 21 | } 22 | ``` 23 | 24 | ### Server-side `config.json` 25 | 26 | - **listen**:The address and port the server will bind to. 27 | - **streams**: The number of streams to be used. 28 | - **remote**: The address of the upstream server. Such as Shadowsocks or VMess or anything else. 29 | ```json 30 | { 31 | "listen": "0.0.0.0:30001", 32 | "streams": 2, 33 | "remote": "127.0.0.1:27429" 34 | } 35 | ``` 36 | 37 | Finally, data sent to port 12811 on the client will be transparently delivered to the service listening on port 27429 on the server. 38 | -------------------------------------------------------------------------------- /cli/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "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 = "async-trait" 22 | version = "0.1.74" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" 25 | dependencies = [ 26 | "proc-macro2", 27 | "quote", 28 | "syn", 29 | ] 30 | 31 | [[package]] 32 | name = "async_async_io" 33 | version = "0.1.3" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "498be45befbbbc159657a62e7762b1c94eb68148fb9a6f213c5e96825e2af563" 36 | dependencies = [ 37 | "async-trait", 38 | "futures-core", 39 | "reusable-box-future", 40 | "tokio", 41 | ] 42 | 43 | [[package]] 44 | name = "autocfg" 45 | version = "1.1.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 48 | 49 | [[package]] 50 | name = "backtrace" 51 | version = "0.3.69" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 54 | dependencies = [ 55 | "addr2line", 56 | "cc", 57 | "cfg-if", 58 | "libc", 59 | "miniz_oxide", 60 | "object", 61 | "rustc-demangle", 62 | ] 63 | 64 | [[package]] 65 | name = "bitflags" 66 | version = "1.3.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 69 | 70 | [[package]] 71 | name = "bytes" 72 | version = "1.5.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 75 | 76 | [[package]] 77 | name = "cc" 78 | version = "1.0.83" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 81 | dependencies = [ 82 | "libc", 83 | ] 84 | 85 | [[package]] 86 | name = "cfg-if" 87 | version = "1.0.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 90 | 91 | [[package]] 92 | name = "cli" 93 | version = "0.1.0" 94 | dependencies = [ 95 | "mptcp", 96 | ] 97 | 98 | [[package]] 99 | name = "futures-core" 100 | version = "0.3.28" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 103 | 104 | [[package]] 105 | name = "gimli" 106 | version = "0.28.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 109 | 110 | [[package]] 111 | name = "hermit-abi" 112 | version = "0.3.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 115 | 116 | [[package]] 117 | name = "libc" 118 | version = "0.2.149" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" 121 | 122 | [[package]] 123 | name = "lock_api" 124 | version = "0.4.11" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 127 | dependencies = [ 128 | "autocfg", 129 | "scopeguard", 130 | ] 131 | 132 | [[package]] 133 | name = "memchr" 134 | version = "2.6.4" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 137 | 138 | [[package]] 139 | name = "miniz_oxide" 140 | version = "0.7.1" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 143 | dependencies = [ 144 | "adler", 145 | ] 146 | 147 | [[package]] 148 | name = "mio" 149 | version = "0.8.8" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 152 | dependencies = [ 153 | "libc", 154 | "wasi", 155 | "windows-sys", 156 | ] 157 | 158 | [[package]] 159 | name = "mptcp" 160 | version = "0.1.0" 161 | dependencies = [ 162 | "async-trait", 163 | "async_async_io", 164 | "bytes", 165 | "scopeguard", 166 | "thiserror", 167 | "tokio", 168 | ] 169 | 170 | [[package]] 171 | name = "num_cpus" 172 | version = "1.16.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 175 | dependencies = [ 176 | "hermit-abi", 177 | "libc", 178 | ] 179 | 180 | [[package]] 181 | name = "object" 182 | version = "0.32.1" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 185 | dependencies = [ 186 | "memchr", 187 | ] 188 | 189 | [[package]] 190 | name = "parking_lot" 191 | version = "0.12.1" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 194 | dependencies = [ 195 | "lock_api", 196 | "parking_lot_core", 197 | ] 198 | 199 | [[package]] 200 | name = "parking_lot_core" 201 | version = "0.9.9" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 204 | dependencies = [ 205 | "cfg-if", 206 | "libc", 207 | "redox_syscall", 208 | "smallvec", 209 | "windows-targets", 210 | ] 211 | 212 | [[package]] 213 | name = "pin-project-lite" 214 | version = "0.2.13" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 217 | 218 | [[package]] 219 | name = "proc-macro2" 220 | version = "1.0.69" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 223 | dependencies = [ 224 | "unicode-ident", 225 | ] 226 | 227 | [[package]] 228 | name = "quote" 229 | version = "1.0.33" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 232 | dependencies = [ 233 | "proc-macro2", 234 | ] 235 | 236 | [[package]] 237 | name = "redox_syscall" 238 | version = "0.4.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 241 | dependencies = [ 242 | "bitflags", 243 | ] 244 | 245 | [[package]] 246 | name = "reusable-box-future" 247 | version = "0.2.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "1e0e61cd21fbddd85fbd9367b775660a01d388c08a61c6d2824af480b0309bb9" 250 | 251 | [[package]] 252 | name = "rustc-demangle" 253 | version = "0.1.23" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 256 | 257 | [[package]] 258 | name = "scopeguard" 259 | version = "1.2.0" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 262 | 263 | [[package]] 264 | name = "signal-hook-registry" 265 | version = "1.4.1" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 268 | dependencies = [ 269 | "libc", 270 | ] 271 | 272 | [[package]] 273 | name = "smallvec" 274 | version = "1.11.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" 277 | 278 | [[package]] 279 | name = "socket2" 280 | version = "0.5.5" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 283 | dependencies = [ 284 | "libc", 285 | "windows-sys", 286 | ] 287 | 288 | [[package]] 289 | name = "syn" 290 | version = "2.0.38" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" 293 | dependencies = [ 294 | "proc-macro2", 295 | "quote", 296 | "unicode-ident", 297 | ] 298 | 299 | [[package]] 300 | name = "thiserror" 301 | version = "1.0.50" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 304 | dependencies = [ 305 | "thiserror-impl", 306 | ] 307 | 308 | [[package]] 309 | name = "thiserror-impl" 310 | version = "1.0.50" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 313 | dependencies = [ 314 | "proc-macro2", 315 | "quote", 316 | "syn", 317 | ] 318 | 319 | [[package]] 320 | name = "tokio" 321 | version = "1.33.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" 324 | dependencies = [ 325 | "backtrace", 326 | "bytes", 327 | "libc", 328 | "mio", 329 | "num_cpus", 330 | "parking_lot", 331 | "pin-project-lite", 332 | "signal-hook-registry", 333 | "socket2", 334 | "tokio-macros", 335 | "windows-sys", 336 | ] 337 | 338 | [[package]] 339 | name = "tokio-macros" 340 | version = "2.1.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 343 | dependencies = [ 344 | "proc-macro2", 345 | "quote", 346 | "syn", 347 | ] 348 | 349 | [[package]] 350 | name = "unicode-ident" 351 | version = "1.0.12" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 354 | 355 | [[package]] 356 | name = "wasi" 357 | version = "0.11.0+wasi-snapshot-preview1" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 360 | 361 | [[package]] 362 | name = "windows-sys" 363 | version = "0.48.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 366 | dependencies = [ 367 | "windows-targets", 368 | ] 369 | 370 | [[package]] 371 | name = "windows-targets" 372 | version = "0.48.5" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 375 | dependencies = [ 376 | "windows_aarch64_gnullvm", 377 | "windows_aarch64_msvc", 378 | "windows_i686_gnu", 379 | "windows_i686_msvc", 380 | "windows_x86_64_gnu", 381 | "windows_x86_64_gnullvm", 382 | "windows_x86_64_msvc", 383 | ] 384 | 385 | [[package]] 386 | name = "windows_aarch64_gnullvm" 387 | version = "0.48.5" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 390 | 391 | [[package]] 392 | name = "windows_aarch64_msvc" 393 | version = "0.48.5" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 396 | 397 | [[package]] 398 | name = "windows_i686_gnu" 399 | version = "0.48.5" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 402 | 403 | [[package]] 404 | name = "windows_i686_msvc" 405 | version = "0.48.5" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 408 | 409 | [[package]] 410 | name = "windows_x86_64_gnu" 411 | version = "0.48.5" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 414 | 415 | [[package]] 416 | name = "windows_x86_64_gnullvm" 417 | version = "0.48.5" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 420 | 421 | [[package]] 422 | name = "windows_x86_64_msvc" 423 | version = "0.48.5" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 426 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | bytes = "1.5.0" 10 | clap = { version = "4.4.6", features = ["derive"] } 11 | mptcp = { path = "../" } 12 | tokio = { version = "1.33.0", features = ["full"] } 13 | anyhow = "1.0.86" 14 | serde = { version = "1.0.164", default-features = false, features = ["derive", "std"] } 15 | serde_json = "1.0.105" -------------------------------------------------------------------------------- /cli/src/bin/client.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, num::NonZeroUsize}; 2 | 3 | use mptcp::stream::MptcpStream; 4 | use tokio::net::TcpListener; 5 | 6 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 7 | pub struct Cli { 8 | /// The server address 9 | pub servers: Vec, 10 | pub local_bind: String, 11 | } 12 | 13 | #[tokio::main] 14 | async fn main() -> anyhow::Result<()> { 15 | let cfg = std::env::args().nth(1).unwrap(); 16 | let args: Cli = serde_json::from_str(tokio::fs::read_to_string(&cfg).await?.as_str())?; 17 | let streams = args.servers.len(); 18 | let local_bind = TcpListener::bind(args.local_bind).await?; 19 | while let Ok((mut s, _)) = local_bind.accept().await { 20 | let servers = args.servers.clone(); 21 | s.set_nodelay(true)?; 22 | tokio::spawn(async move { 23 | let address = servers 24 | .into_iter() 25 | .map(|x| x.parse::().unwrap()); 26 | let mut stream = MptcpStream::connect(address, NonZeroUsize::new(streams).unwrap()) 27 | .await 28 | .unwrap(); 29 | let _ = tokio::io::copy_bidirectional(&mut s, &mut stream).await; 30 | }); 31 | } 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /cli/src/bin/server.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, num::NonZeroUsize}; 2 | 3 | use mptcp::listen::MptcpListener; 4 | use tokio::net::TcpStream; 5 | 6 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 7 | pub struct Cli { 8 | /// The listen address 9 | pub listen: String, 10 | pub streams: NonZeroUsize, 11 | pub remote: String, 12 | } 13 | 14 | #[tokio::main] 15 | async fn main() -> anyhow::Result<()> { 16 | let cfg = std::env::args().nth(1).unwrap(); 17 | let args: Cli = serde_json::from_str(tokio::fs::read_to_string(&cfg).await?.as_str())?; 18 | let bind_addr = args.listen.parse::()?; 19 | let mut listener = MptcpListener::bind(bind_addr, args.streams).await.unwrap(); 20 | while let Ok(mut s) = listener.accept().await { 21 | let remote = args.remote.parse::()?; 22 | tokio::spawn(async move { 23 | let mut remote_stream = TcpStream::connect(remote).await?; 24 | remote_stream.set_nodelay(true)?; 25 | let _ = tokio::io::copy_bidirectional(&mut remote_stream, &mut s).await; 26 | Ok::<_, anyhow::Error>(()) 27 | }); 28 | } 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | path::{Path, PathBuf}, 4 | }; 5 | 6 | use clap::{Args, Subcommand}; 7 | use tokio::{ 8 | fs::File, 9 | io::{AsyncRead, AsyncWrite, BufReader}, 10 | }; 11 | 12 | #[derive(Debug, Clone, Subcommand)] 13 | pub enum FileTransferCommand { 14 | Push(PushFileArgs), 15 | Pull(PullFileArgs), 16 | } 17 | 18 | impl FileTransferCommand { 19 | pub async fn perform( 20 | &self, 21 | read: impl AsyncRead + Unpin, 22 | write: impl AsyncWrite + Unpin, 23 | ) -> io::Result { 24 | match self { 25 | FileTransferCommand::Push(args) => args.push_file(write).await, 26 | FileTransferCommand::Pull(args) => args.pull_file(read).await, 27 | } 28 | } 29 | } 30 | 31 | #[derive(Debug, Clone, Args)] 32 | pub struct PushFileArgs { 33 | pub source_file: PathBuf, 34 | } 35 | 36 | impl PushFileArgs { 37 | pub async fn push_file(&self, write: impl AsyncWrite + Unpin) -> io::Result { 38 | push_file(&self.source_file, write).await 39 | } 40 | } 41 | 42 | pub async fn push_file( 43 | source_file: impl AsRef, 44 | mut write: impl AsyncWrite + Unpin, 45 | ) -> io::Result { 46 | let file = File::open(source_file).await.unwrap(); 47 | let mut file = BufReader::new(file); 48 | 49 | let read = tokio::io::copy(&mut file, &mut write).await.unwrap(); 50 | 51 | Ok(usize::try_from(read).unwrap()) 52 | } 53 | 54 | #[derive(Debug, Clone, Args)] 55 | pub struct PullFileArgs { 56 | pub output_file: PathBuf, 57 | } 58 | 59 | impl PullFileArgs { 60 | pub async fn pull_file(&self, read: impl AsyncRead + Unpin) -> io::Result { 61 | pull_file(&self.output_file, read).await 62 | } 63 | } 64 | 65 | pub async fn pull_file( 66 | output_file: impl AsRef, 67 | mut read: impl AsyncRead + Unpin, 68 | ) -> io::Result { 69 | let _ = tokio::fs::remove_file(&output_file).await; 70 | let mut file = File::options() 71 | .write(true) 72 | .create(true) 73 | .open(output_file) 74 | .await 75 | .unwrap(); 76 | 77 | let written = tokio::io::copy(&mut read, &mut file).await.unwrap(); 78 | 79 | Ok(usize::try_from(written).unwrap()) 80 | } 81 | 82 | pub fn print_performance_statistics(n: usize, duration: std::time::Duration) { 83 | let throughput = n as f64 / duration.as_secs_f64(); 84 | let throughput_mib_s = throughput / 1024. / 1024.; 85 | let latency_ms = duration.as_secs_f64() * 1000.; 86 | println!("throughput: {throughput_mib_s:.2} MiB/s, latency: {latency_ms:.2} ms"); 87 | } 88 | 89 | pub enum Protocol { 90 | Tcp, 91 | Mptcp { streams: std::num::NonZeroUsize }, 92 | } 93 | 94 | impl std::str::FromStr for Protocol { 95 | type Err = (); 96 | 97 | fn from_str(s: &str) -> Result { 98 | if s == "tcp" { 99 | return Ok(Self::Tcp); 100 | } 101 | if s.starts_with("mptcp.") { 102 | let (_, streams) = s.split_once('.').ok_or(())?; 103 | let streams = streams.parse().map_err(|_| ())?; 104 | return Ok(Self::Mptcp { streams }); 105 | } 106 | Err(()) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /img/session.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackLuny/mptcp/1ea32825243c64696c7b22672113c6e37a577bf9/img/session.drawio.png -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod listen; 2 | pub mod message; 3 | pub mod receiver; 4 | pub mod recv_buf; 5 | pub mod send_buf; 6 | pub mod sender; 7 | pub mod stream; 8 | 9 | pub use listen::MptcpListener; 10 | pub use stream::{MptcpStream, OwnedReadHalf, OwnedWriteHalf, ReadHalf, WriteHalf}; 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | 15 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 16 | 17 | use crate::{receiver::Receiver, sender::Sender}; 18 | 19 | #[tokio::test] 20 | async fn test_sender_receiver_1() { 21 | let streams = 4; 22 | let mut send_streams = vec![]; 23 | let mut recv_streams = vec![]; 24 | for _ in 0..streams { 25 | let (tx, rx) = tokio::io::duplex(64); 26 | send_streams.push(tx); 27 | recv_streams.push(rx); 28 | } 29 | let sender = Sender::new(send_streams); 30 | let receiver = Receiver::new(recv_streams); 31 | 32 | let mut async_write = sender.into_async_write(); 33 | let mut async_read = receiver.into_async_read(); 34 | 35 | let msg = b"hello world"; 36 | async_write.write_all(msg).await.unwrap(); 37 | let mut buf = vec![0; msg.len()]; 38 | async_read.read_exact(&mut buf).await.unwrap(); 39 | 40 | assert_eq!(&msg[..], &buf); 41 | 42 | drop(async_write); 43 | let n = async_read.read(&mut buf).await.unwrap(); 44 | assert_eq!(n, 0); 45 | } 46 | 47 | #[tokio::test] 48 | async fn test_sender_receiver_2() { 49 | let streams = 4; 50 | let mut send_streams = vec![]; 51 | let mut recv_streams = vec![]; 52 | for _ in 0..streams { 53 | let (tx, rx) = tokio::io::duplex(64); 54 | send_streams.push(tx); 55 | recv_streams.push(rx); 56 | } 57 | let sender = Sender::new(send_streams); 58 | let receiver = Receiver::new(recv_streams); 59 | 60 | let mut async_write = sender.into_async_write(); 61 | let mut async_read = receiver.into_async_read(); 62 | 63 | let msg = b"0"; 64 | async_write.write_all(msg).await.unwrap(); 65 | let mut buf = vec![0; msg.len()]; 66 | async_read.read_exact(&mut buf).await.unwrap(); 67 | 68 | assert_eq!(&msg[..], &buf); 69 | 70 | drop(async_write); 71 | let n = async_read.read(&mut buf).await.unwrap(); 72 | assert_eq!(n, 0); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/listen.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | io, 4 | net::SocketAddr, 5 | num::NonZeroUsize, 6 | sync::{Arc, RwLock}, 7 | time::{Duration, Instant}, 8 | }; 9 | 10 | use tokio::{ 11 | net::{tcp, TcpListener, TcpStream, ToSocketAddrs}, 12 | task::JoinSet, 13 | }; 14 | 15 | use crate::{ 16 | message::{Init, Session}, 17 | stream::{MptcpStream, SingleAddress}, 18 | }; 19 | 20 | const BACKLOG_TIMEOUT: Duration = Duration::from_secs(60); 21 | const INIT_TIMEOUT: Duration = Duration::from_secs(1); 22 | const BACKLOG_MAX: usize = 64; 23 | 24 | #[derive(Debug)] 25 | pub struct MptcpListener { 26 | listener: Arc, 27 | complete: tokio::sync::mpsc::Receiver>, 28 | _tasks: JoinSet<()>, 29 | } 30 | 31 | impl MptcpListener { 32 | pub async fn bind( 33 | addr: impl ToSocketAddrs, 34 | max_session_streams: NonZeroUsize, 35 | ) -> io::Result { 36 | let listener = Arc::new(TcpListener::bind(addr).await?); 37 | let (tx, rx) = tokio::sync::mpsc::channel(BACKLOG_MAX); 38 | 39 | let backlog = Arc::new(Backlog::new( 40 | NonZeroUsize::new(BACKLOG_MAX).unwrap(), 41 | max_session_streams, 42 | )); 43 | 44 | let mut tasks = JoinSet::new(); 45 | for _ in 0..BACKLOG_MAX { 46 | let backlog = Arc::clone(&backlog); 47 | let listener = Arc::clone(&listener); 48 | let complete = tx.clone(); 49 | tasks.spawn(async move { 50 | loop { 51 | let (stream, _) = match listener.accept().await { 52 | Ok(x) => x, 53 | Err(e) => { 54 | let _ = complete.send(Err(e)).await; 55 | continue; 56 | } 57 | }; 58 | let _ = stream.set_nodelay(true); 59 | backlog.handle(stream, &complete).await; 60 | } 61 | }); 62 | } 63 | tasks.spawn({ 64 | let backlog = Arc::clone(&backlog); 65 | async move { 66 | loop { 67 | tokio::time::sleep(BACKLOG_TIMEOUT / 2).await; 68 | 69 | backlog.clean(BACKLOG_TIMEOUT); 70 | } 71 | } 72 | }); 73 | 74 | Ok(Self { 75 | listener, 76 | complete: rx, 77 | _tasks: tasks, 78 | }) 79 | } 80 | 81 | /// # Cancel safety 82 | /// 83 | /// This method is cancel safe. If `accept` is used as the event in a `tokio::select` statement and some other branch completes first, it is guaranteed that no streams were received on this listener. 84 | pub async fn accept(&mut self) -> io::Result { 85 | self.complete 86 | .recv() 87 | .await 88 | .expect("senders will never drop proactively") 89 | } 90 | 91 | pub fn local_addr(&self) -> io::Result { 92 | self.listener.local_addr() 93 | } 94 | } 95 | 96 | #[derive(Debug)] 97 | struct Backlog { 98 | queued: RwLock>, 99 | queue_max: NonZeroUsize, 100 | max_session_streams: NonZeroUsize, 101 | } 102 | 103 | impl Backlog { 104 | pub fn new(queue_max: NonZeroUsize, max_session_streams: NonZeroUsize) -> Self { 105 | Self { 106 | queued: RwLock::new(HashMap::new()), 107 | queue_max, 108 | max_session_streams, 109 | } 110 | } 111 | 112 | pub fn clean(&self, timeout: Duration) { 113 | self.queued 114 | .write() 115 | .unwrap() 116 | .retain(|_, v: &mut QueuedConnection| !v.timed_out(timeout)); 117 | } 118 | 119 | pub async fn handle( 120 | &self, 121 | stream: TcpStream, 122 | complete: &tokio::sync::mpsc::Sender>, 123 | ) { 124 | async fn handle_impl( 125 | this: &Backlog, 126 | mut stream: TcpStream, 127 | complete: &tokio::sync::mpsc::Sender>, 128 | ) -> io::Result<()> { 129 | let init = tokio::time::timeout(INIT_TIMEOUT, Init::decode(&mut stream)).await??; 130 | if init.streams() > this.max_session_streams { 131 | return Ok(()); 132 | } 133 | 134 | let stream = loop { 135 | let mut queued = this.queued.write().unwrap(); 136 | match queued.remove(&init.session()) { 137 | Some(queued_connection) => { 138 | let res = queued_connection.push(stream); 139 | match res { 140 | QueuedConnectionPushResult::QueuedConnection(queued_connection) => { 141 | queued.insert(init.session(), queued_connection); 142 | break None; 143 | } 144 | QueuedConnectionPushResult::Stream(stream) => break Some(stream), 145 | } 146 | } 147 | None => { 148 | if queued.len() >= this.queue_max.get() { 149 | break None; 150 | } 151 | let queued_connection = QueuedConnection::new(init.streams()); 152 | queued.insert(init.session(), queued_connection); 153 | } 154 | } 155 | }; 156 | if let Some(stream) = stream { 157 | let _ = complete.send(Ok(stream)).await; 158 | } 159 | Ok(()) 160 | } 161 | let _ = handle_impl(self, stream, complete).await; 162 | } 163 | } 164 | 165 | #[derive(Debug)] 166 | struct QueuedConnection { 167 | read_streams: Vec, 168 | write_streams: Vec, 169 | max: NonZeroUsize, 170 | last_update: Instant, 171 | } 172 | 173 | impl QueuedConnection { 174 | pub fn new(number: NonZeroUsize) -> Self { 175 | Self { 176 | read_streams: Vec::new(), 177 | write_streams: Vec::new(), 178 | max: number, 179 | last_update: Instant::now(), 180 | } 181 | } 182 | 183 | pub fn push(mut self, stream: TcpStream) -> QueuedConnectionPushResult { 184 | let addr = SingleAddress::Local(stream.local_addr().unwrap()); 185 | let (read, write) = stream.into_split(); 186 | self.read_streams.push(read); 187 | self.write_streams.push(write); 188 | if self.read_streams.len() == self.max.get() { 189 | let stream = MptcpStream::new(self.read_streams, self.write_streams, addr); 190 | return QueuedConnectionPushResult::Stream(stream); 191 | } 192 | self.last_update = Instant::now(); 193 | QueuedConnectionPushResult::QueuedConnection(self) 194 | } 195 | 196 | pub fn timed_out(&self, timeout: Duration) -> bool { 197 | self.last_update.elapsed() > timeout 198 | } 199 | } 200 | 201 | #[derive(Debug)] 202 | enum QueuedConnectionPushResult { 203 | QueuedConnection(QueuedConnection), 204 | Stream(MptcpStream), 205 | } 206 | -------------------------------------------------------------------------------- /src/message.rs: -------------------------------------------------------------------------------- 1 | use std::{io, num::NonZeroUsize}; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 5 | 6 | const DATA_SEGMENT_TYPE_CODE: u8 = 0; 7 | const PING_TYPE_CODE: u8 = 1; 8 | const SHUTDOWN_TYPE_CODE: u8 = 2; 9 | 10 | #[derive(Debug)] 11 | pub enum Message { 12 | DataSegment(DataSegment), 13 | Ping, 14 | Shutdown, 15 | } 16 | 17 | impl Message { 18 | pub async fn encode(&self, writer: &mut W) -> io::Result<()> 19 | where 20 | W: AsyncWrite + Unpin, 21 | { 22 | match self { 23 | Message::DataSegment(data_segment) => { 24 | writer.write_u8(DATA_SEGMENT_TYPE_CODE).await?; 25 | data_segment.encode(writer).await?; 26 | } 27 | Message::Ping => writer.write_u8(PING_TYPE_CODE).await?, 28 | Message::Shutdown => writer.write_u8(SHUTDOWN_TYPE_CODE).await?, 29 | } 30 | writer.flush().await?; 31 | Ok(()) 32 | } 33 | 34 | pub async fn decode(reader: &mut R) -> io::Result 35 | where 36 | R: AsyncRead + Unpin, 37 | { 38 | let type_code = reader.read_u8().await?; 39 | let this = match type_code { 40 | DATA_SEGMENT_TYPE_CODE => { 41 | let data_segment = DataSegment::decode(reader).await?; 42 | Self::DataSegment(data_segment) 43 | } 44 | PING_TYPE_CODE => Self::Ping, 45 | SHUTDOWN_TYPE_CODE => Self::Shutdown, 46 | _ => { 47 | return Err(io::Error::new( 48 | io::ErrorKind::InvalidData, 49 | format!("unknown type code: {type_code}"), 50 | )) 51 | } 52 | }; 53 | Ok(this) 54 | } 55 | } 56 | 57 | #[derive(Debug)] 58 | pub struct DataSegment { 59 | start_sequence: Sequence, 60 | payload: Bytes, 61 | } 62 | 63 | impl DataSegment { 64 | pub fn new(start_sequence: Sequence, payload: Bytes) -> Option { 65 | if payload.is_empty() { 66 | return None; 67 | } 68 | 69 | Some(Self { 70 | start_sequence, 71 | payload, 72 | }) 73 | } 74 | 75 | pub fn start_sequence(&self) -> Sequence { 76 | self.start_sequence 77 | } 78 | 79 | pub fn end_sequence(&self) -> Sequence { 80 | Sequence::new(self.start_sequence.inner() + self.payload.len() as u64) 81 | } 82 | 83 | pub fn payload(&self) -> &Bytes { 84 | &self.payload 85 | } 86 | 87 | pub async fn encode(&self, writer: &mut W) -> io::Result<()> 88 | where 89 | W: AsyncWrite + Unpin, 90 | { 91 | writer.write_u64(self.start_sequence.inner()).await?; 92 | writer.write_u16(self.payload.len() as u16).await?; 93 | writer.write_all(&self.payload).await?; 94 | Ok(()) 95 | } 96 | 97 | pub fn advance(mut self, bytes: usize) -> Option { 98 | if self.payload.len() <= bytes { 99 | return None; 100 | } 101 | 102 | self.start_sequence = Sequence::new(self.start_sequence.inner() + bytes as u64); 103 | self.payload.advance(bytes); 104 | 105 | Some(self) 106 | } 107 | 108 | pub fn advance_to(self, end: Sequence) -> Option { 109 | let bytes = end.inner().saturating_sub(self.start_sequence.inner()); 110 | let bytes = match usize::try_from(bytes) { 111 | Ok(bytes) => bytes, 112 | Err(_) => return None, 113 | }; 114 | 115 | self.advance(bytes) 116 | } 117 | 118 | pub fn size(&self) -> usize { 119 | self.payload.len() 120 | } 121 | 122 | pub async fn decode(reader: &mut R) -> io::Result 123 | where 124 | R: AsyncRead + Unpin, 125 | { 126 | let start_sequence = reader.read_u64().await?; 127 | let length = reader.read_u16().await?; 128 | let length = 129 | usize::try_from(length).map_err(|e| io::Error::new(io::ErrorKind::Unsupported, e))?; 130 | let mut payload = BytesMut::with_capacity(length); 131 | payload.put_bytes(0, length); 132 | reader.read_exact(&mut payload[..]).await?; 133 | let this = Self::new(Sequence::new(start_sequence), payload.into()) 134 | .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid data segment"))?; 135 | Ok(this) 136 | } 137 | } 138 | 139 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, std::hash::Hash)] 140 | pub struct Sequence(u64); 141 | 142 | impl Sequence { 143 | pub fn new(number: u64) -> Self { 144 | Self(number) 145 | } 146 | 147 | pub fn inner(&self) -> u64 { 148 | self.0 149 | } 150 | } 151 | 152 | #[derive(Debug, Clone)] 153 | pub struct Init { 154 | session: Session, 155 | streams: NonZeroUsize, 156 | } 157 | 158 | impl Init { 159 | pub fn new(session: Session, streams: NonZeroUsize) -> Self { 160 | Self { session, streams } 161 | } 162 | 163 | pub fn session(&self) -> Session { 164 | self.session 165 | } 166 | 167 | pub fn streams(&self) -> NonZeroUsize { 168 | self.streams 169 | } 170 | 171 | pub async fn encode(&self, writer: &mut W) -> io::Result<()> 172 | where 173 | W: AsyncWrite + Unpin, 174 | { 175 | let mut buf = [0_u8; 8 * 2]; 176 | let mut buf_writer = io::Cursor::new(&mut buf[..]); 177 | 178 | buf_writer.write_u64(self.session.inner()).await.unwrap(); 179 | buf_writer 180 | .write_u64(self.streams.get() as u64) 181 | .await 182 | .unwrap(); 183 | 184 | writer.write_all(&buf).await?; 185 | writer.flush().await?; 186 | Ok(()) 187 | } 188 | 189 | pub async fn decode(reader: &mut R) -> io::Result 190 | where 191 | R: AsyncRead + Unpin, 192 | { 193 | let mut buf = [0_u8; 8 * 2]; 194 | reader.read_exact(&mut buf).await?; 195 | let mut buf_reader = io::Cursor::new(&buf[..]); 196 | 197 | let session = buf_reader.read_u64().await.unwrap(); 198 | let session = Session::new(session); 199 | let streams = buf_reader.read_u64().await.unwrap(); 200 | let streams = 201 | usize::try_from(streams).map_err(|e| io::Error::new(io::ErrorKind::Unsupported, e))?; 202 | let streams = NonZeroUsize::new(streams) 203 | .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "zero streams"))?; 204 | Ok(Self { session, streams }) 205 | } 206 | } 207 | 208 | #[derive(Debug, Clone, Copy, PartialEq, Eq, std::hash::Hash)] 209 | pub struct Session(u64); 210 | 211 | impl Session { 212 | pub fn new(number: u64) -> Self { 213 | Self(number) 214 | } 215 | 216 | pub fn inner(&self) -> u64 { 217 | self.0 218 | } 219 | } 220 | 221 | #[cfg(test)] 222 | mod tests { 223 | use super::*; 224 | 225 | #[tokio::test] 226 | async fn test_data_segment_codec() { 227 | let src = 228 | DataSegment::new(Sequence(42), Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef])).unwrap(); 229 | let mut buf = vec![]; 230 | src.encode(&mut buf).await.unwrap(); 231 | let mut reader = io::Cursor::new(&buf[..]); 232 | let dst = DataSegment::decode(&mut reader).await.unwrap(); 233 | assert_eq!(src.start_sequence(), dst.start_sequence()); 234 | assert_eq!(src.payload(), dst.payload()); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/receiver.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | sync::{Arc, Mutex, RwLock}, 4 | time::Duration, 5 | }; 6 | 7 | use async_async_io::read::{AsyncAsyncRead, PollRead}; 8 | use thiserror::Error; 9 | use tokio::{ 10 | io::{AsyncRead, AsyncReadExt}, 11 | select, 12 | sync::{mpsc, Notify}, 13 | task::JoinSet, 14 | }; 15 | 16 | use crate::{ 17 | message::{DataSegment, Message}, 18 | recv_buf::RecvStreamBuf, 19 | }; 20 | 21 | const LINGER: Duration = Duration::from_secs(10); 22 | 23 | #[derive(Debug)] 24 | pub struct Receiver { 25 | recv_buf: Arc>, 26 | recv_buf_inserted: Arc, 27 | leftover_data_segment: Option, 28 | last_io_error: Arc>>, 29 | recv_tasks: JoinSet<()>, 30 | _closed: mpsc::Receiver<()>, 31 | } 32 | 33 | impl Receiver { 34 | pub fn new(streams: Vec) -> Self 35 | where 36 | R: AsyncRead + Unpin + Send + 'static, 37 | { 38 | let recv_buf = Arc::new(RwLock::new(RecvStreamBuf::new())); 39 | let recv_buf_inserted = Arc::new(Notify::new()); 40 | let last_io_error = Arc::new(Mutex::new(None)); 41 | let (closed_tx, closed_rx) = mpsc::channel(1); 42 | 43 | let mut recv_tasks = JoinSet::new(); 44 | for mut stream in streams { 45 | let recv_buf_inserted = recv_buf_inserted.clone(); 46 | let recv_buf = recv_buf.clone(); 47 | let last_io_error = last_io_error.clone(); 48 | let closed_tx = closed_tx.clone(); 49 | recv_tasks.spawn(async move { 50 | loop { 51 | let res = select! { 52 | () = closed_tx.closed() => { 53 | // Prevent triggering TCP RST from our side 54 | let mut drain_task = JoinSet::new(); 55 | drain_task.spawn(async move { 56 | let mut buf = [0; 1]; 57 | loop { 58 | let n = stream.read(&mut buf).await?; 59 | if n == 0 { 60 | break; 61 | } 62 | } 63 | Ok::<_, io::Error>(()) 64 | }); 65 | 66 | tokio::select! { 67 | res = drain_task.join_next() => { 68 | if let Some(task) = res { 69 | // In case the task panicked 70 | let _ = task.unwrap(); 71 | } 72 | } 73 | () = tokio::time::sleep(LINGER) => (), 74 | } 75 | break; 76 | } 77 | // `Message::decode` is NOT cancel safe but it's OK if it will not be called again 78 | res = Message::decode(&mut stream) => res, 79 | }; 80 | 81 | let message = match res { 82 | Ok(message) => message, 83 | Err(e) => { 84 | let mut last_io_error = last_io_error.lock().unwrap(); 85 | *last_io_error = Some(e); 86 | break; 87 | } 88 | }; 89 | let data_segment = match message { 90 | Message::DataSegment(data_segment) => data_segment, 91 | Message::Ping => continue, 92 | Message::Shutdown => { 93 | let mut last_io_error = last_io_error.lock().unwrap(); 94 | *last_io_error = None; 95 | break; 96 | } 97 | }; 98 | 99 | { 100 | let mut recv_buf = recv_buf.write().unwrap(); 101 | recv_buf.insert(data_segment); 102 | } 103 | 104 | recv_buf_inserted.notify_waiters(); 105 | } 106 | }); 107 | } 108 | 109 | Self { 110 | recv_buf, 111 | recv_buf_inserted, 112 | leftover_data_segment: None, 113 | last_io_error, 114 | recv_tasks, 115 | _closed: closed_rx, 116 | } 117 | } 118 | 119 | pub async fn recv(&mut self, buf: &mut [u8]) -> io::Result { 120 | loop { 121 | let leftover_data_segment = self.leftover_data_segment.take(); 122 | 123 | let mut handle_data_segment = |data_segment: DataSegment| { 124 | let readable = buf.len().min(data_segment.size()); 125 | buf[..readable].copy_from_slice(&data_segment.payload()[..readable]); 126 | self.leftover_data_segment = data_segment.advance(readable); 127 | Ok(readable) 128 | }; 129 | 130 | if let Some(data_segment) = leftover_data_segment { 131 | return handle_data_segment(data_segment); 132 | } 133 | 134 | // Checkout receive buffer 135 | let recv_buf_inserted = self.recv_buf_inserted.notified(); 136 | { 137 | let mut recv_buf = self.recv_buf.write().unwrap(); 138 | if let Some(data_segment) = recv_buf.pop_first() { 139 | drop(recv_buf); 140 | return handle_data_segment(data_segment); 141 | } 142 | } 143 | 144 | tokio::select! { 145 | () = recv_buf_inserted => (), 146 | res = self.recv_tasks.join_next() => { 147 | if let Some(task) = res { 148 | task.unwrap(); 149 | continue; 150 | } 151 | 152 | let mut last_io_error = self.last_io_error.lock().unwrap(); 153 | match last_io_error.take() { 154 | Some(e) => return Err(e), 155 | None => return Ok(0), 156 | } 157 | } 158 | } 159 | } 160 | } 161 | 162 | pub fn into_async_read(self) -> PollRead { 163 | PollRead::new(self) 164 | } 165 | } 166 | 167 | impl Drop for Receiver { 168 | fn drop(&mut self) { 169 | // The drop of `self.closed` will signal receive tasks to end later 170 | self.recv_tasks.detach_all(); 171 | } 172 | } 173 | 174 | impl AsyncAsyncRead for Receiver { 175 | async fn read(&mut self, buf: &mut [u8]) -> io::Result { 176 | match self.recv(buf).await { 177 | Ok(n) => Ok(n), 178 | Err(_) => Ok(0), 179 | } 180 | } 181 | } 182 | 183 | #[derive(Debug, Error)] 184 | #[error("No stream left")] 185 | pub struct NoStreamLeft; 186 | -------------------------------------------------------------------------------- /src/recv_buf.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::message::{DataSegment, Sequence}; 4 | 5 | #[derive(Debug)] 6 | pub struct RecvStreamBuf { 7 | next: Sequence, 8 | data_segments: BTreeMap, 9 | } 10 | 11 | impl RecvStreamBuf { 12 | pub fn new() -> Self { 13 | Self { 14 | next: Sequence::new(0), 15 | data_segments: BTreeMap::new(), 16 | } 17 | } 18 | 19 | pub fn insert(&mut self, data_segment: DataSegment) { 20 | // Remove stale data 21 | let Some(data_segment) = data_segment.advance_to(self.next) else { 22 | return; 23 | }; 24 | 25 | // Resolve key conflict 26 | if let Some(old_data_segment) = self.data_segments.get(&data_segment.start_sequence()) { 27 | if data_segment.size() <= old_data_segment.size() { 28 | return; 29 | } 30 | } 31 | 32 | self.data_segments 33 | .insert(data_segment.start_sequence(), data_segment); 34 | } 35 | 36 | pub fn pop_first(&mut self) -> Option { 37 | let (first_key, _) = self.data_segments.first_key_value()?; 38 | if *first_key != self.next { 39 | return None; 40 | } 41 | 42 | let (_, first_segment) = self.data_segments.pop_first().unwrap(); 43 | 44 | self.next = first_segment.end_sequence(); 45 | 46 | // Deduplicate data 47 | loop { 48 | if let Some((start_sequence, _)) = self.data_segments.first_key_value() { 49 | if self.next <= *start_sequence { 50 | break; 51 | } 52 | } 53 | let Some((_, data_segment)) = self.data_segments.pop_first() else { 54 | break; 55 | }; 56 | 57 | let data_segment = data_segment.advance_to(self.next); 58 | if let Some(data_segment) = data_segment { 59 | self.data_segments 60 | .insert(data_segment.start_sequence(), data_segment); 61 | } 62 | } 63 | 64 | Some(first_segment) 65 | } 66 | } 67 | 68 | impl Default for RecvStreamBuf { 69 | fn default() -> Self { 70 | Self::new() 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use bytes::Bytes; 77 | 78 | use super::*; 79 | 80 | #[test] 81 | fn basic() { 82 | let mut buf = RecvStreamBuf::new(); 83 | buf.insert(DataSegment::new(Sequence::new(0), Bytes::from_iter(vec![0, 1, 2])).unwrap()); 84 | let data_segment = buf.pop_first().unwrap(); 85 | assert_eq!(buf.next, data_segment.end_sequence()); 86 | assert!(buf.pop_first().is_none()); 87 | } 88 | 89 | #[test] 90 | fn unordered() { 91 | let mut buf = RecvStreamBuf::new(); 92 | buf.insert(DataSegment::new(Sequence::new(1), Bytes::from_iter(vec![1])).unwrap()); 93 | assert!(buf.pop_first().is_none()); 94 | buf.insert(DataSegment::new(Sequence::new(2), Bytes::from_iter(vec![2])).unwrap()); 95 | assert!(buf.pop_first().is_none()); 96 | buf.insert(DataSegment::new(Sequence::new(0), Bytes::from_iter(vec![0])).unwrap()); 97 | let data_segment = buf.pop_first().unwrap(); 98 | assert_eq!(data_segment.start_sequence(), Sequence::new(0)); 99 | assert_eq!(buf.next, Sequence::new(1)); 100 | let data_segment = buf.pop_first().unwrap(); 101 | assert_eq!(data_segment.start_sequence(), Sequence::new(1)); 102 | assert_eq!(buf.next, Sequence::new(2)); 103 | let data_segment = buf.pop_first().unwrap(); 104 | assert_eq!(data_segment.start_sequence(), Sequence::new(2)); 105 | assert_eq!(buf.next, Sequence::new(3)); 106 | assert!(buf.pop_first().is_none()); 107 | } 108 | 109 | #[test] 110 | fn remove_stale_data() { 111 | let mut buf = RecvStreamBuf::new(); 112 | buf.insert(DataSegment::new(Sequence::new(0), Bytes::from_iter(vec![0, 1, 2])).unwrap()); 113 | let _ = buf.pop_first().unwrap(); 114 | buf.insert( 115 | DataSegment::new(Sequence::new(0), Bytes::from_iter(vec![0, 1, 2, 3, 4])).unwrap(), 116 | ); 117 | let data_segment = buf.pop_first().unwrap(); 118 | assert_eq!(data_segment.start_sequence(), Sequence::new(3)); 119 | assert_eq!(data_segment.size(), 2); 120 | assert_eq!(buf.next, data_segment.end_sequence()); 121 | assert!(buf.pop_first().is_none()); 122 | } 123 | 124 | #[test] 125 | fn deduplicate_1() { 126 | let mut buf = RecvStreamBuf::new(); 127 | buf.insert(DataSegment::new(Sequence::new(0), Bytes::from_iter(vec![0, 1, 2])).unwrap()); 128 | buf.insert(DataSegment::new(Sequence::new(1), Bytes::from_iter(vec![1, 2])).unwrap()); 129 | let _ = buf.pop_first().unwrap(); 130 | assert_eq!(buf.next, Sequence::new(3)); 131 | assert!(buf.pop_first().is_none()); 132 | } 133 | 134 | #[test] 135 | fn deduplicate_2() { 136 | let mut buf = RecvStreamBuf::new(); 137 | buf.insert(DataSegment::new(Sequence::new(0), Bytes::from_iter(vec![0, 1, 2])).unwrap()); 138 | buf.insert(DataSegment::new(Sequence::new(1), Bytes::from_iter(vec![1, 2, 3])).unwrap()); 139 | let _ = buf.pop_first().unwrap(); 140 | assert_eq!(buf.next, Sequence::new(3)); 141 | let data_segment = buf.pop_first().unwrap(); 142 | assert_eq!(data_segment.size(), 1); 143 | assert_eq!(data_segment.start_sequence(), Sequence::new(3)); 144 | assert_eq!(buf.next, Sequence::new(4)); 145 | assert!(buf.pop_first().is_none()); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/send_buf.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use bytes::Bytes; 4 | 5 | use crate::message::{DataSegment, Sequence}; 6 | 7 | const MINIMUM_PAYLOAD_SIZE: usize = 8192; 8 | 9 | #[derive(Debug)] 10 | pub struct SendStreamBuf { 11 | data: Bytes, 12 | unsent_segments: BTreeMap, 13 | start_sequence: Sequence, 14 | } 15 | 16 | impl SendStreamBuf { 17 | pub fn new(data: Bytes, start_sequence: Sequence) -> Self { 18 | let mut unsent = BTreeMap::new(); 19 | if !data.is_empty() { 20 | unsent.insert(start_sequence, data.len()); 21 | } 22 | Self { 23 | data, 24 | unsent_segments: unsent, 25 | start_sequence, 26 | } 27 | } 28 | 29 | pub fn done(&self) -> bool { 30 | self.unsent_segments.is_empty() 31 | } 32 | 33 | /// Best-effect 34 | pub fn split_first_unsent_segment(&mut self, segments: usize) { 35 | if self.unsent_segments.len() >= segments { 36 | return; 37 | } 38 | let Some((sequence, length)) = self.unsent_segments.pop_first() else { 39 | return; 40 | }; 41 | 42 | let segment_bytes = length.div_ceil(segments).max(MINIMUM_PAYLOAD_SIZE); 43 | if segment_bytes == 0 { 44 | return; 45 | } 46 | 47 | // e.g., 48 | // `| 17 |` 49 | // `| 6 | 6 | 5 |` 50 | let mut next_sequence = sequence; 51 | let mut remaining_bytes = length; 52 | while remaining_bytes > 0 { 53 | let length = segment_bytes.min(remaining_bytes); 54 | self.unsent_segments.insert(next_sequence, length); 55 | remaining_bytes -= length; 56 | next_sequence = Sequence::new(next_sequence.inner() + length as u64); 57 | } 58 | } 59 | 60 | pub fn iter_unsent_segments(&self) -> impl Iterator + '_ { 61 | self.unsent_segments.iter().map(|(start_sequence, length)| { 62 | let start = start_sequence.inner() - self.start_sequence.inner(); 63 | let start = usize::try_from(start).unwrap(); 64 | let range = start..(start + *length); 65 | let payload = self.data.slice(range); 66 | DataSegment::new(*start_sequence, payload).unwrap() 67 | }) 68 | } 69 | 70 | pub fn mark_as_sent(&mut self, sequence: Sequence) { 71 | self.unsent_segments.remove(&sequence); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/sender.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::VecDeque, io}; 2 | 3 | use async_async_io::write::{AsyncAsyncWrite, PollWrite}; 4 | use bytes::Bytes; 5 | use thiserror::Error; 6 | use tokio::{ 7 | io::{AsyncWrite, AsyncWriteExt}, 8 | task::JoinSet, 9 | }; 10 | 11 | use crate::{ 12 | message::{Message, Sequence}, 13 | send_buf::SendStreamBuf, 14 | }; 15 | 16 | /// You will have to explicitly call `Self::shutdown` before the drop 17 | #[derive(Debug)] 18 | pub struct Sender { 19 | streams: VecDeque, 20 | next: Sequence, 21 | } 22 | 23 | impl Sender 24 | where 25 | W: AsyncWrite + Unpin + Send + 'static, 26 | { 27 | pub fn new(streams: Vec) -> Self { 28 | Self { 29 | streams: streams.into(), 30 | next: Sequence::new(0), 31 | } 32 | } 33 | 34 | pub async fn batch_send(&mut self, send_buf: &mut SendStreamBuf) -> Result<(), SendError> { 35 | if self.streams.is_empty() { 36 | return Err(SendError::NoStreamLeft); 37 | } 38 | 39 | let mut write_tasks: JoinSet> = JoinSet::new(); 40 | let segments = send_buf.iter_unsent_segments(); 41 | 42 | for segment in segments { 43 | let Some(mut stream) = self.streams.pop_front() else { 44 | break; 45 | }; 46 | 47 | write_tasks.spawn(async move { 48 | let start_sequence = segment.start_sequence(); 49 | 50 | let message = Message::DataSegment(segment); 51 | message.encode(&mut stream).await?; 52 | 53 | Ok((Some(start_sequence), stream)) 54 | }); 55 | } 56 | 57 | // Send pings for the remaining streams 58 | while let Some(mut stream) = self.streams.pop_front() { 59 | write_tasks.spawn(async move { 60 | Message::Ping.encode(&mut stream).await?; 61 | 62 | Ok((None, stream)) 63 | }); 64 | } 65 | 66 | let mut io_errors = vec![]; 67 | while let Some(task) = write_tasks.join_next().await { 68 | let res = task.unwrap(); 69 | match res { 70 | Ok((Some(sequence), stream)) => { 71 | self.streams.push_back(stream); 72 | send_buf.mark_as_sent(sequence); 73 | } 74 | Ok((None, stream)) => { 75 | self.streams.push_back(stream); 76 | } 77 | Err(e) => { 78 | io_errors.push(e); 79 | } 80 | } 81 | } 82 | if !io_errors.is_empty() { 83 | return Err(SendError::Io(io_errors)); 84 | } 85 | Ok(()) 86 | } 87 | 88 | pub async fn batch_send_all(&mut self, data: Bytes) -> Result<(), NoStreamLeft> { 89 | let data_len = data.len(); 90 | let mut send_buf = SendStreamBuf::new(data, self.next); 91 | send_buf.split_first_unsent_segment(self.streams.len()); 92 | 93 | loop { 94 | let res = self.batch_send(&mut send_buf).await; 95 | match res { 96 | Ok(()) => (), 97 | Err(SendError::NoStreamLeft) => return Err(NoStreamLeft), 98 | _ => continue, 99 | } 100 | if send_buf.done() { 101 | self.next = Sequence::new(self.next.inner() + data_len as u64); 102 | return Ok(()); 103 | } 104 | } 105 | } 106 | 107 | pub fn into_async_write(self) -> PollWrite { 108 | PollWrite::new(self) 109 | } 110 | 111 | pub async fn shutdown(&mut self) -> io::Result<()> { 112 | let mut last_io_error = None; 113 | for stream in &mut self.streams { 114 | if let Err(e) = shutdown_stream(stream).await { 115 | last_io_error = Some(e); 116 | } 117 | } 118 | match last_io_error { 119 | Some(e) => Err(e), 120 | None => Ok(()), 121 | } 122 | } 123 | } 124 | 125 | impl AsyncAsyncWrite for Sender 126 | where 127 | W: AsyncWrite + Unpin + Send + 'static, 128 | { 129 | async fn write(&mut self, buf: &[u8]) -> io::Result { 130 | { 131 | // SAFETY: `data` will be dropped outside of this scope 132 | let data = Bytes::from_static(unsafe { std::mem::transmute(buf) }); 133 | self.batch_send_all(data) 134 | .await 135 | .map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e))?; 136 | } 137 | Ok(buf.len()) 138 | } 139 | 140 | async fn flush(&mut self) -> io::Result<()> { 141 | for stream in &mut self.streams { 142 | stream.flush().await?; 143 | } 144 | Ok(()) 145 | } 146 | 147 | async fn shutdown(&mut self) -> io::Result<()> { 148 | Self::shutdown(self).await 149 | } 150 | } 151 | 152 | async fn shutdown_stream(stream: &mut W) -> io::Result<()> 153 | where 154 | W: AsyncWrite + Unpin, 155 | { 156 | let message = Message::Shutdown; 157 | message.encode(stream).await?; 158 | stream.shutdown().await?; 159 | Ok(()) 160 | } 161 | 162 | #[derive(Debug, Error)] 163 | pub enum SendError { 164 | #[error("No stream left")] 165 | NoStreamLeft, 166 | #[error("Stream I/O errors")] 167 | Io(Vec), 168 | } 169 | 170 | #[derive(Debug, Error)] 171 | #[error("No stream left")] 172 | pub struct NoStreamLeft; 173 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | use std::{io, net::SocketAddr, num::NonZeroUsize, pin::Pin}; 2 | 3 | use async_async_io::{read::PollRead, write::PollWrite, PollIo}; 4 | use tokio::{ 5 | io::{AsyncRead, AsyncWrite}, 6 | net::{tcp, TcpStream, ToSocketAddrs}, 7 | task::JoinSet, 8 | }; 9 | 10 | use crate::{ 11 | message::{Init, Session}, 12 | receiver::Receiver, 13 | sender::Sender, 14 | }; 15 | 16 | #[derive(Debug)] 17 | pub struct MptcpStream { 18 | poll: PollIo>, 19 | addr: SingleAddress, 20 | } 21 | 22 | impl MptcpStream { 23 | pub(crate) fn new( 24 | read_streams: Vec, 25 | write_streams: Vec, 26 | addr: SingleAddress, 27 | ) -> Self { 28 | let sender = Sender::new(write_streams); 29 | let receiver = Receiver::new(read_streams); 30 | let poll = PollIo::new(PollRead::new(receiver), PollWrite::new(sender)); 31 | Self { poll, addr } 32 | } 33 | 34 | pub async fn connect( 35 | addr: impl IntoIterator, 36 | streams: NonZeroUsize, 37 | ) -> io::Result { 38 | let mut address = addr.into_iter(); 39 | let mut read_streams = vec![]; 40 | let mut write_streams = vec![]; 41 | let session: u64 = rand::random(); 42 | let session = Session::new(session); 43 | let init = Init::new(session, streams); 44 | 45 | let mut connections = JoinSet::new(); 46 | for _ in 0..streams.get() { 47 | let addr = address.next().unwrap(); 48 | let init = init.clone(); 49 | connections.spawn(async move { 50 | let mut stream = TcpStream::connect(&addr).await?; 51 | stream.set_nodelay(true)?; 52 | let peer_addr = stream.peer_addr().unwrap(); 53 | init.encode(&mut stream).await?; 54 | Ok::<_, io::Error>((stream, peer_addr)) 55 | }); 56 | } 57 | 58 | let mut last_peer_addr = None; 59 | while let Some(task) = connections.join_next().await { 60 | let (stream, peer_addr) = task.unwrap()?; 61 | last_peer_addr = Some(peer_addr); 62 | let (read, write) = stream.into_split(); 63 | read_streams.push(read); 64 | write_streams.push(write); 65 | } 66 | 67 | let addr = SingleAddress::Peer(last_peer_addr.unwrap()); 68 | 69 | Ok(Self::new(read_streams, write_streams, addr)) 70 | } 71 | 72 | pub fn into_split(self) -> (OwnedReadHalf, OwnedWriteHalf) { 73 | let (read, write) = self.poll.into_split(); 74 | let addr = self.addr; 75 | let read = OwnedReadHalf { poll: read, addr }; 76 | let write = OwnedWriteHalf { poll: write, addr }; 77 | (read, write) 78 | } 79 | 80 | pub fn split(&mut self) -> (ReadHalf, WriteHalf) { 81 | let (read, write) = self.poll.split_mut(); 82 | let addr = self.addr; 83 | let read = ReadHalf { poll: read, addr }; 84 | let write = WriteHalf { poll: write, addr }; 85 | (read, write) 86 | } 87 | 88 | pub fn local_addr(&self) -> Option { 89 | self.addr.local() 90 | } 91 | 92 | pub fn peer_addr(&self) -> Option { 93 | self.addr.peer() 94 | } 95 | } 96 | 97 | impl AsyncRead for MptcpStream { 98 | fn poll_read( 99 | mut self: std::pin::Pin<&mut Self>, 100 | cx: &mut std::task::Context<'_>, 101 | buf: &mut tokio::io::ReadBuf<'_>, 102 | ) -> std::task::Poll> { 103 | Pin::new(&mut self.poll).poll_read(cx, buf) 104 | } 105 | } 106 | 107 | impl AsyncWrite for MptcpStream { 108 | fn poll_write( 109 | mut self: Pin<&mut Self>, 110 | cx: &mut std::task::Context<'_>, 111 | buf: &[u8], 112 | ) -> std::task::Poll> { 113 | Pin::new(&mut self.poll).poll_write(cx, buf) 114 | } 115 | 116 | fn poll_flush( 117 | mut self: Pin<&mut Self>, 118 | cx: &mut std::task::Context<'_>, 119 | ) -> std::task::Poll> { 120 | Pin::new(&mut self.poll).poll_flush(cx) 121 | } 122 | 123 | fn poll_shutdown( 124 | mut self: Pin<&mut Self>, 125 | cx: &mut std::task::Context<'_>, 126 | ) -> std::task::Poll> { 127 | Pin::new(&mut self.poll).poll_shutdown(cx) 128 | } 129 | } 130 | 131 | #[derive(Debug)] 132 | pub struct OwnedReadHalf { 133 | poll: PollRead, 134 | addr: SingleAddress, 135 | } 136 | 137 | impl OwnedReadHalf { 138 | pub fn reunite(self, write: OwnedWriteHalf) -> MptcpStream { 139 | let poll = PollIo::new(self.poll, write.poll); 140 | let addr = self.addr; 141 | MptcpStream { poll, addr } 142 | } 143 | 144 | pub fn local_addr(&self) -> Option { 145 | self.addr.local() 146 | } 147 | 148 | pub fn peer_addr(&self) -> Option { 149 | self.addr.peer() 150 | } 151 | } 152 | 153 | impl AsyncRead for OwnedReadHalf { 154 | fn poll_read( 155 | mut self: std::pin::Pin<&mut Self>, 156 | cx: &mut std::task::Context<'_>, 157 | buf: &mut tokio::io::ReadBuf<'_>, 158 | ) -> std::task::Poll> { 159 | Pin::new(&mut self.poll).poll_read(cx, buf) 160 | } 161 | } 162 | 163 | #[derive(Debug)] 164 | pub struct OwnedWriteHalf { 165 | poll: PollWrite>, 166 | addr: SingleAddress, 167 | } 168 | 169 | impl OwnedWriteHalf { 170 | pub fn reunite(self, read: OwnedReadHalf) -> MptcpStream { 171 | let poll = PollIo::new(read.poll, self.poll); 172 | let addr = self.addr; 173 | MptcpStream { poll, addr } 174 | } 175 | 176 | pub fn local_addr(&self) -> Option { 177 | self.addr.local() 178 | } 179 | 180 | pub fn peer_addr(&self) -> Option { 181 | self.addr.peer() 182 | } 183 | } 184 | 185 | impl AsyncWrite for OwnedWriteHalf { 186 | fn poll_write( 187 | mut self: Pin<&mut Self>, 188 | cx: &mut std::task::Context<'_>, 189 | buf: &[u8], 190 | ) -> std::task::Poll> { 191 | Pin::new(&mut self.poll).poll_write(cx, buf) 192 | } 193 | 194 | fn poll_flush( 195 | mut self: Pin<&mut Self>, 196 | cx: &mut std::task::Context<'_>, 197 | ) -> std::task::Poll> { 198 | Pin::new(&mut self.poll).poll_flush(cx) 199 | } 200 | 201 | fn poll_shutdown( 202 | mut self: Pin<&mut Self>, 203 | cx: &mut std::task::Context<'_>, 204 | ) -> std::task::Poll> { 205 | Pin::new(&mut self.poll).poll_shutdown(cx) 206 | } 207 | } 208 | 209 | #[derive(Debug)] 210 | pub struct ReadHalf<'poll> { 211 | poll: &'poll mut PollRead, 212 | addr: SingleAddress, 213 | } 214 | 215 | impl ReadHalf<'_> { 216 | pub fn local_addr(&self) -> Option { 217 | self.addr.local() 218 | } 219 | 220 | pub fn peer_addr(&self) -> Option { 221 | self.addr.peer() 222 | } 223 | } 224 | 225 | impl AsyncRead for ReadHalf<'_> { 226 | fn poll_read( 227 | mut self: std::pin::Pin<&mut Self>, 228 | cx: &mut std::task::Context<'_>, 229 | buf: &mut tokio::io::ReadBuf<'_>, 230 | ) -> std::task::Poll> { 231 | Pin::new(&mut self.poll).poll_read(cx, buf) 232 | } 233 | } 234 | 235 | #[derive(Debug)] 236 | pub struct WriteHalf<'poll> { 237 | poll: &'poll mut PollWrite>, 238 | addr: SingleAddress, 239 | } 240 | 241 | impl WriteHalf<'_> { 242 | pub fn local_addr(&self) -> Option { 243 | self.addr.local() 244 | } 245 | 246 | pub fn peer_addr(&self) -> Option { 247 | self.addr.peer() 248 | } 249 | } 250 | 251 | impl AsyncWrite for WriteHalf<'_> { 252 | fn poll_write( 253 | mut self: Pin<&mut Self>, 254 | cx: &mut std::task::Context<'_>, 255 | buf: &[u8], 256 | ) -> std::task::Poll> { 257 | Pin::new(&mut self.poll).poll_write(cx, buf) 258 | } 259 | 260 | fn poll_flush( 261 | mut self: Pin<&mut Self>, 262 | cx: &mut std::task::Context<'_>, 263 | ) -> std::task::Poll> { 264 | Pin::new(&mut self.poll).poll_flush(cx) 265 | } 266 | 267 | fn poll_shutdown( 268 | mut self: Pin<&mut Self>, 269 | cx: &mut std::task::Context<'_>, 270 | ) -> std::task::Poll> { 271 | Pin::new(&mut self.poll).poll_shutdown(cx) 272 | } 273 | } 274 | 275 | #[derive(Debug, Clone, Copy)] 276 | pub(crate) enum SingleAddress { 277 | Local(SocketAddr), 278 | Peer(SocketAddr), 279 | } 280 | 281 | impl SingleAddress { 282 | pub fn local(&self) -> Option { 283 | match self { 284 | SingleAddress::Local(local) => Some(*local), 285 | SingleAddress::Peer(_) => None, 286 | } 287 | } 288 | 289 | pub fn peer(&self) -> Option { 290 | match self { 291 | SingleAddress::Local(_) => None, 292 | SingleAddress::Peer(peer) => Some(*peer), 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /tests/bench.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, num::NonZeroUsize, process::exit, time::Instant}; 2 | 3 | use bytes::BytesMut; 4 | use mptcp::{MptcpListener, MptcpStream}; 5 | use tokio::{ 6 | io::{AsyncReadExt, AsyncWriteExt}, 7 | task::JoinSet, 8 | }; 9 | 10 | const STREAMS: usize = 4; 11 | const PAYLOAD_SIZE: usize = 1 << 24; 12 | 13 | async fn bench_server() -> (SocketAddr, JoinSet<()>) { 14 | let mut server = MptcpListener::bind("127.0.0.1:0", NonZeroUsize::new(STREAMS).unwrap()) 15 | .await 16 | .unwrap(); 17 | let listen_addr = server.local_addr().unwrap(); 18 | let mut listen_task = JoinSet::new(); 19 | listen_task.spawn(async move { 20 | let mut buf = BytesMut::new(); 21 | let mut stream = server.accept().await.unwrap(); 22 | loop { 23 | stream.read_buf(&mut buf).await.unwrap(); 24 | } 25 | }); 26 | (listen_addr, listen_task) 27 | } 28 | 29 | async fn bench_client(listen_addr: SocketAddr) { 30 | const ROUNDS: usize = 100; 31 | let mut client = MptcpStream::connect(listen_addr, NonZeroUsize::new(STREAMS).unwrap()) 32 | .await 33 | .unwrap(); 34 | let buf: Vec = (0..PAYLOAD_SIZE).map(|_| rand::random()).collect(); 35 | let start = Instant::now(); 36 | for _ in 0..ROUNDS { 37 | client.write_all(&buf).await.unwrap(); 38 | } 39 | let duration = start.elapsed(); 40 | let throughput = (buf.len() * ROUNDS) as f64 / duration.as_secs_f64(); 41 | let throughput_mib_s = throughput / 1024. / 1024.; 42 | let latency_ms = duration.as_secs_f64() * 1000. / ROUNDS as f64; 43 | println!("throughput: {throughput_mib_s:.2} MiB/s, latency: {latency_ms:.2} ms"); 44 | } 45 | 46 | #[ignore] 47 | #[tokio::test(flavor = "multi_thread", worker_threads = 4)] 48 | async fn bench() { 49 | let (addr, _task) = bench_server().await; 50 | bench_client(addr).await; 51 | exit(0); 52 | } 53 | --------------------------------------------------------------------------------