├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── data ├── http-req └── http-resp ├── rustfmt.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "autocfg" 36 | version = "1.1.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "1.3.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 45 | 46 | [[package]] 47 | name = "bytes" 48 | version = "1.1.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 51 | 52 | [[package]] 53 | name = "cfg-if" 54 | version = "0.1.10" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 57 | 58 | [[package]] 59 | name = "cfg-if" 60 | version = "1.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 63 | 64 | [[package]] 65 | name = "clap" 66 | version = "2.34.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 69 | dependencies = [ 70 | "ansi_term", 71 | "atty", 72 | "bitflags", 73 | "strsim", 74 | "textwrap", 75 | "unicode-width", 76 | "vec_map", 77 | ] 78 | 79 | [[package]] 80 | name = "env_logger" 81 | version = "0.9.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 84 | dependencies = [ 85 | "atty", 86 | "humantime", 87 | "log", 88 | "regex", 89 | "termcolor", 90 | ] 91 | 92 | [[package]] 93 | name = "err-derive" 94 | version = "0.3.1" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e" 97 | dependencies = [ 98 | "proc-macro-error", 99 | "proc-macro2", 100 | "quote", 101 | "rustversion", 102 | "syn", 103 | "synstructure", 104 | ] 105 | 106 | [[package]] 107 | name = "futures" 108 | version = "0.3.21" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 111 | dependencies = [ 112 | "futures-channel", 113 | "futures-core", 114 | "futures-executor", 115 | "futures-io", 116 | "futures-sink", 117 | "futures-task", 118 | "futures-util", 119 | ] 120 | 121 | [[package]] 122 | name = "futures-channel" 123 | version = "0.3.21" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 126 | dependencies = [ 127 | "futures-core", 128 | "futures-sink", 129 | ] 130 | 131 | [[package]] 132 | name = "futures-core" 133 | version = "0.3.21" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 136 | 137 | [[package]] 138 | name = "futures-executor" 139 | version = "0.3.21" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 142 | dependencies = [ 143 | "futures-core", 144 | "futures-task", 145 | "futures-util", 146 | ] 147 | 148 | [[package]] 149 | name = "futures-io" 150 | version = "0.3.21" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 153 | 154 | [[package]] 155 | name = "futures-macro" 156 | version = "0.3.21" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 159 | dependencies = [ 160 | "proc-macro2", 161 | "quote", 162 | "syn", 163 | ] 164 | 165 | [[package]] 166 | name = "futures-sink" 167 | version = "0.3.21" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 170 | 171 | [[package]] 172 | name = "futures-task" 173 | version = "0.3.21" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 176 | 177 | [[package]] 178 | name = "futures-util" 179 | version = "0.3.21" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 182 | dependencies = [ 183 | "futures-channel", 184 | "futures-core", 185 | "futures-io", 186 | "futures-macro", 187 | "futures-sink", 188 | "futures-task", 189 | "memchr", 190 | "pin-project-lite", 191 | "pin-utils", 192 | "slab", 193 | ] 194 | 195 | [[package]] 196 | name = "heck" 197 | version = "0.3.3" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 200 | dependencies = [ 201 | "unicode-segmentation", 202 | ] 203 | 204 | [[package]] 205 | name = "hermit-abi" 206 | version = "0.1.19" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 209 | dependencies = [ 210 | "libc", 211 | ] 212 | 213 | [[package]] 214 | name = "humantime" 215 | version = "2.1.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 218 | 219 | [[package]] 220 | name = "lazy_static" 221 | version = "1.4.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 224 | 225 | [[package]] 226 | name = "libc" 227 | version = "0.2.126" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 230 | 231 | [[package]] 232 | name = "lock_api" 233 | version = "0.4.7" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 236 | dependencies = [ 237 | "autocfg", 238 | "scopeguard", 239 | ] 240 | 241 | [[package]] 242 | name = "log" 243 | version = "0.4.17" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 246 | dependencies = [ 247 | "cfg-if 1.0.0", 248 | ] 249 | 250 | [[package]] 251 | name = "memchr" 252 | version = "2.5.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 255 | 256 | [[package]] 257 | name = "mio" 258 | version = "0.8.3" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" 261 | dependencies = [ 262 | "libc", 263 | "log", 264 | "wasi", 265 | "windows-sys", 266 | ] 267 | 268 | [[package]] 269 | name = "net2" 270 | version = "0.2.37" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 273 | dependencies = [ 274 | "cfg-if 0.1.10", 275 | "libc", 276 | "winapi", 277 | ] 278 | 279 | [[package]] 280 | name = "num_cpus" 281 | version = "1.13.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 284 | dependencies = [ 285 | "hermit-abi", 286 | "libc", 287 | ] 288 | 289 | [[package]] 290 | name = "once_cell" 291 | version = "1.12.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 294 | 295 | [[package]] 296 | name = "parking_lot" 297 | version = "0.12.1" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 300 | dependencies = [ 301 | "lock_api", 302 | "parking_lot_core", 303 | ] 304 | 305 | [[package]] 306 | name = "parking_lot_core" 307 | version = "0.9.3" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 310 | dependencies = [ 311 | "cfg-if 1.0.0", 312 | "libc", 313 | "redox_syscall", 314 | "smallvec", 315 | "windows-sys", 316 | ] 317 | 318 | [[package]] 319 | name = "pin-project-lite" 320 | version = "0.2.9" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 323 | 324 | [[package]] 325 | name = "pin-utils" 326 | version = "0.1.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 329 | 330 | [[package]] 331 | name = "proc-macro-error" 332 | version = "1.0.4" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 335 | dependencies = [ 336 | "proc-macro-error-attr", 337 | "proc-macro2", 338 | "quote", 339 | "syn", 340 | "version_check", 341 | ] 342 | 343 | [[package]] 344 | name = "proc-macro-error-attr" 345 | version = "1.0.4" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 348 | dependencies = [ 349 | "proc-macro2", 350 | "quote", 351 | "version_check", 352 | ] 353 | 354 | [[package]] 355 | name = "proc-macro2" 356 | version = "1.0.39" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" 359 | dependencies = [ 360 | "unicode-ident", 361 | ] 362 | 363 | [[package]] 364 | name = "quote" 365 | version = "1.0.18" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 368 | dependencies = [ 369 | "proc-macro2", 370 | ] 371 | 372 | [[package]] 373 | name = "redox_syscall" 374 | version = "0.2.13" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 377 | dependencies = [ 378 | "bitflags", 379 | ] 380 | 381 | [[package]] 382 | name = "regex" 383 | version = "1.5.6" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" 386 | dependencies = [ 387 | "aho-corasick", 388 | "memchr", 389 | "regex-syntax", 390 | ] 391 | 392 | [[package]] 393 | name = "regex-syntax" 394 | version = "0.6.26" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" 397 | 398 | [[package]] 399 | name = "rustversion" 400 | version = "1.0.6" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" 403 | 404 | [[package]] 405 | name = "scopeguard" 406 | version = "1.1.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 409 | 410 | [[package]] 411 | name = "signal-hook-registry" 412 | version = "1.4.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 415 | dependencies = [ 416 | "libc", 417 | ] 418 | 419 | [[package]] 420 | name = "slab" 421 | version = "0.4.6" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" 424 | 425 | [[package]] 426 | name = "smallvec" 427 | version = "1.8.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 430 | 431 | [[package]] 432 | name = "socket2" 433 | version = "0.4.4" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 436 | dependencies = [ 437 | "libc", 438 | "winapi", 439 | ] 440 | 441 | [[package]] 442 | name = "strsim" 443 | version = "0.8.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 446 | 447 | [[package]] 448 | name = "structopt" 449 | version = "0.3.26" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 452 | dependencies = [ 453 | "clap", 454 | "lazy_static", 455 | "structopt-derive", 456 | ] 457 | 458 | [[package]] 459 | name = "structopt-derive" 460 | version = "0.4.18" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 463 | dependencies = [ 464 | "heck", 465 | "proc-macro-error", 466 | "proc-macro2", 467 | "quote", 468 | "syn", 469 | ] 470 | 471 | [[package]] 472 | name = "syn" 473 | version = "1.0.96" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" 476 | dependencies = [ 477 | "proc-macro2", 478 | "quote", 479 | "unicode-ident", 480 | ] 481 | 482 | [[package]] 483 | name = "synstructure" 484 | version = "0.12.6" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 487 | dependencies = [ 488 | "proc-macro2", 489 | "quote", 490 | "syn", 491 | "unicode-xid", 492 | ] 493 | 494 | [[package]] 495 | name = "termcolor" 496 | version = "1.1.3" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 499 | dependencies = [ 500 | "winapi-util", 501 | ] 502 | 503 | [[package]] 504 | name = "textwrap" 505 | version = "0.11.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 508 | dependencies = [ 509 | "unicode-width", 510 | ] 511 | 512 | [[package]] 513 | name = "tokio" 514 | version = "1.19.2" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" 517 | dependencies = [ 518 | "bytes", 519 | "libc", 520 | "memchr", 521 | "mio", 522 | "num_cpus", 523 | "once_cell", 524 | "parking_lot", 525 | "pin-project-lite", 526 | "signal-hook-registry", 527 | "socket2", 528 | "tokio-macros", 529 | "winapi", 530 | ] 531 | 532 | [[package]] 533 | name = "tokio-macros" 534 | version = "1.8.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 537 | dependencies = [ 538 | "proc-macro2", 539 | "quote", 540 | "syn", 541 | ] 542 | 543 | [[package]] 544 | name = "tspam" 545 | version = "0.1.0" 546 | dependencies = [ 547 | "env_logger", 548 | "err-derive", 549 | "futures", 550 | "log", 551 | "net2", 552 | "once_cell", 553 | "structopt", 554 | "tokio", 555 | ] 556 | 557 | [[package]] 558 | name = "unicode-ident" 559 | version = "1.0.0" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 562 | 563 | [[package]] 564 | name = "unicode-segmentation" 565 | version = "1.9.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 568 | 569 | [[package]] 570 | name = "unicode-width" 571 | version = "0.1.9" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 574 | 575 | [[package]] 576 | name = "unicode-xid" 577 | version = "0.2.3" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" 580 | 581 | [[package]] 582 | name = "vec_map" 583 | version = "0.8.2" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 586 | 587 | [[package]] 588 | name = "version_check" 589 | version = "0.9.4" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 592 | 593 | [[package]] 594 | name = "wasi" 595 | version = "0.11.0+wasi-snapshot-preview1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 598 | 599 | [[package]] 600 | name = "winapi" 601 | version = "0.3.9" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 604 | dependencies = [ 605 | "winapi-i686-pc-windows-gnu", 606 | "winapi-x86_64-pc-windows-gnu", 607 | ] 608 | 609 | [[package]] 610 | name = "winapi-i686-pc-windows-gnu" 611 | version = "0.4.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 614 | 615 | [[package]] 616 | name = "winapi-util" 617 | version = "0.1.5" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 620 | dependencies = [ 621 | "winapi", 622 | ] 623 | 624 | [[package]] 625 | name = "winapi-x86_64-pc-windows-gnu" 626 | version = "0.4.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 629 | 630 | [[package]] 631 | name = "windows-sys" 632 | version = "0.36.1" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 635 | dependencies = [ 636 | "windows_aarch64_msvc", 637 | "windows_i686_gnu", 638 | "windows_i686_msvc", 639 | "windows_x86_64_gnu", 640 | "windows_x86_64_msvc", 641 | ] 642 | 643 | [[package]] 644 | name = "windows_aarch64_msvc" 645 | version = "0.36.1" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 648 | 649 | [[package]] 650 | name = "windows_i686_gnu" 651 | version = "0.36.1" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 654 | 655 | [[package]] 656 | name = "windows_i686_msvc" 657 | version = "0.36.1" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 660 | 661 | [[package]] 662 | name = "windows_x86_64_gnu" 663 | version = "0.36.1" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 666 | 667 | [[package]] 668 | name = "windows_x86_64_msvc" 669 | version = "0.36.1" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 672 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tspam" 3 | version = "0.1.0" 4 | authors = ["Michal 'vorner' Vaner "] 5 | edition = "2018" 6 | license = "Apache-2.0/MIT" 7 | 8 | [dependencies] 9 | env_logger = "~0.9" 10 | err-derive = "~0.3" 11 | futures = "0.3" 12 | log = "~0.4" 13 | net2 = "~0.2" 14 | once_cell = "~1" 15 | structopt = "~0.3" 16 | tokio = { version = "1", features=["full"] } 17 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 tokio-jsonrpc developers 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. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TCP Spammer 2 | 3 | This tool allows measuring link performance by torturing it with huge numbers of 4 | new connections. 5 | 6 | There are tools that allow measuring link throughput in terms of throughput in 7 | bytes, like iperf. However, if the link contains something *smart*, not just a 8 | dumb wire (eg. a stateful firewall, router with NAT), there's a big difference 9 | between making a single TCP connection and sending a lot of data through it 10 | and creating a lot of new connections, each transferring only little bit of 11 | data. I was interested in the latter. 12 | 13 | There probably are tools for that too, but I wanted to play, so I wrote this. 14 | 15 | ## Maturity, maintenance, contributing... 16 | 17 | It gets the job done. It isn't something you would want in production, but the 18 | purpose is obviously not production, but testing and it's fine for that. 19 | 20 | As it gets done what I needed, I don't expect to be extending or maintaining it 21 | much. 22 | 23 | That being said, if you find it useful, need a little feature or want to take it 24 | over, feel free to open an issue or a pull request. I'll try to respond and 25 | discuss the needs. 26 | 27 | ## Working principle 28 | 29 | There's a server and there's a client. The client connects to the server, sends 30 | a bit of data, closes its half of connection, then the server sends bit of data 31 | and closes the other half of the connection. That's it. 32 | 33 | However, all this can be done massively in parallel. 34 | 35 | The server has only one mode, where it simply listens and answers connections as 36 | fast as it can. 37 | 38 | The client has two modes. One with manual setting of rate. Rate is how many new 39 | connections are made per second ‒ they are created and latency statistics of the 40 | whole connection turn around is measured (minimum, maximum, mean and 90th 41 | percentile). 42 | 43 | In the second mode the client tries to find at which rate the link gets 44 | saturated. It increases the rate in steps and tries to detect significant rise 45 | in the latency median. 46 | 47 | All the parameters are configurable on command line. 48 | 49 | ## Performance 50 | 51 | When dry-testing, the tool was able to saturate a gigabit ethernet link with 3kB 52 | payloads on ordinary commodity hardware. It was around 25k connections per 53 | second. This is expected to be enough to flood any kind of ordinary *smart* 54 | device. 55 | 56 | However, to do so, it is necessary to tweak some OS settings. The problem is the 57 | existence of too many parallel and new connections. 58 | 59 | * Raise the limit of number of file descriptors, eg. `ulimit -n 100000`, both on 60 | the server and client side. 61 | * Allow as many ports usable on the client side as possible: 62 | `echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range`. 63 | * Allow reusing of ports in `TIME_WAIT` state: 64 | `echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse`. 65 | * Raise the maximum number of waiting TCP connections on the server side: 66 | `echo 20480 > /proc/sys/net/ipv4/tcp_max_syn_backlog`. 67 | 68 | Even in these settings, weird things were sometimes happening, eg: 69 | * Some connections were getting resetted. 70 | * Sometimes, the OS still run out of available ports. Use of the `--cooldown` 71 | parameter might help a bit, or using a shorter `--length` of the test. 72 | * Sometimes, *something* in the kernel switches and starts consuming a lot of 73 | CPU while dropping the performance significantly. I didn't manage to find out 74 | what exactly, but I believe it is related to running out of the available 75 | local ports on the client, or nearly so. 76 | 77 | ## Installation 78 | 79 | Compile with Rust version 1.39 or newer (or nightly, if 1.39 is not released yet 80 | 😇). 81 | 82 | ``` 83 | cargo +nightly install --git https://github.com/vorner/tspam 84 | ``` 85 | 86 | Or simply run it: 87 | 88 | ``` 89 | cargo +nightly run --release -- --help 90 | ``` 91 | 92 | ## License 93 | 94 | Licensed under either of 95 | 96 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 97 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 98 | 99 | at your option. 100 | -------------------------------------------------------------------------------- /data/http-req: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host: vorner.cz 3 | User-Agent: curl/7.66.0 4 | Accept: */* 5 | 6 | -------------------------------------------------------------------------------- /data/http-resp: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Date: Mon, 16 Sep 2019 12:04:04 GMT 3 | Server: Apache/2.4.25 (Debian) 4 | Last-Modified: Thu, 09 Feb 2017 19:51:53 GMT 5 | ETag: "142d-5481e501e8f00" 6 | Accept-Ranges: bytes 7 | Content-Length: 5165 8 | Vary: Accept-Encoding 9 | Content-Type: text/html; charset=UTF-8 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Michal 'vorner' Vaner - Osobn.. str..nky 21 | 22 | 23 |

Michal 'vorner' Vaner - Osobn.. str..nky

24 |
50 |
51 |

O m..

52 |

53 | V..t..ina lid.., kte.... najdou tuto str..nku, m.. u.. znaj... Pro ty ostatn.., jsem 54 | program..tor s asi trochu zvl....tn..m smyslem pro humor. R..d se pohybuji v p....rod.., 55 | a to jak vodorovn.. po sv..ch tak svisle za asistence lana. 56 |

57 | Zam..stn..n.. aktu..ln.. nechci (nebo.. jedno m..m), na n..jak.. men.... projekty, 58 | konzutace, kurzy a podobn.. v..ci se nech..m p..emluvit. Z..jemci o takov.. v..ci si 59 | mohou pro....st ..ivotopis. 60 |

61 |
62 |

Kontakt

63 |

64 | M....e.. m.. kontaktovat mnoha r..zn..mi zp..soby, z nich.. n..kter.. jsou vyjmenovan.. zde. 65 |

72 |

73 | N..kter.. z t..chto zp..sob.. umo....uj.. ..ifrov..n.. pomoc.. PGP. Pokud pos..l.... cokoliv d..v..rn..ho (hesla, ....sla bankovn..ch ....t.., pl..ny na z..chranu sv..ta p..ed Microsoftem...), vyu..ij t..to mo..nosti. 74 | Fingerprint m..ho ve..ejn..ho kl....e je 460B 4CF5 E995 864B 71D7 19B7 8CB8 0BB7 F823 3AEA. 75 |

76 | Rozhodn.. mi nepi.. na adresu vorner@uu.ucw.cz, ta adresa je nebezpe..n.., stejn.. jako v..echny na t..to str..nce. 77 |

78 | Co se t....e zach..zen.. s t..mito kontakty: pokud m.... skv..l.. n..pad mi poslat pozv..nku na n..jakou n..hodnou internetovou 79 | aplikaci, bez kter.. nemohu ....t, ..i n..co podobn..ho, ud..lej tak nap....mo ... pokud vypln.... moji emailovou 80 | adresu do n..jak..ho ok..nka na t..to n..hodn.. aplikaci na rozes..l..n.. spamu, uv..dom si, ..e t..m: 81 |

    82 |
  • Poru...... z..kon, nebo.. se na emailov.. adresy vztahuje ochrana osobn..ch ..daj... 83 |
  • Chov.... se jako ovce, kter.. nep..em....l.. a ud..l.. cokoliv, co ti n..kdo nap....e. Pokud je tomu opravdu tak, pros..m po..li 84 | sv.. ....slo ....tu a heslo do internetov..ho bankovnictv.. na n..kter.. z v....e uveden..ch kontakt... 85 |
  • Po....dn.. m.. t..m na..tve.. a pokud zjist..m, ..e jsi to byl ty, pravd..podobn.. ti p..kn.. od srdce ..eknu, co si o tom mysl..m. 86 |
87 |
88 |
89 |

O str..nk..ch

90 |

91 | Tyto str..nky byly vytvo..eny za pomoci editoru Vim, n..stroje GNU make a n..kolika skript.. v Bashi a Perlu. 92 | Pokud stoj.... o jejich p..vodn.. zdrojovou podobu, tak si nap..ed rozmysli, jestli opravdu chce.. ....st hieroglyfy v Perlu a pokud ano, pak mi napi... 93 |

94 | Najde..-li zde chybu gramatickou, stylistickou, v..cnou, syntaktickou ..i jinou, napi.. mi EMail. 95 | Pokud ti ale str..nky nefunguj.. v hr..ze zvan.. Internet Explorer, pak nepi.. mn.., ale: 96 |

    97 |
  • Napi.. Microsoftu, aby tu v..c opravil 98 |
  • Se..e.. si prohl....e.., nap....klad Mozilla Firefox 99 |

    100 | Neposkytuji ....dnou z..ruku na aktu..lnost, pravdivost nebo u..ite..nost 101 | informac.. nalezen..ch na t..chto str..nk..ch. Jednodu..e, mnoh.. bylo naps..no 102 | p..ed lety a od t.. doby jsem to po sob.. ne..etl. 103 |

104 |
105 |
106 | 107 | 108 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vorner/tspam/cb0b8186c8685a1300d8335758959da34c89339e/rustfmt.toml -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter, Result as FmtResult}; 2 | use std::fs; 3 | use std::io::Error as IoError; 4 | use std::mem; 5 | use std::net::{IpAddr, SocketAddr}; 6 | use std::path::PathBuf; 7 | use std::pin::Pin; 8 | use std::process; 9 | use std::sync::Arc; 10 | use std::task::{Context, Poll}; 11 | use std::thread; 12 | use std::time::Duration; 13 | 14 | use err_derive::Error; 15 | use futures::future; 16 | use log::{debug, error, info, warn}; 17 | use net2::unix::UnixTcpBuilderExt; 18 | use net2::TcpBuilder; 19 | use once_cell::sync::Lazy; 20 | use structopt::StructOpt; 21 | use tokio::io::{self, AsyncWrite, AsyncWriteExt}; 22 | use tokio::net::{TcpListener, TcpStream}; 23 | use tokio::runtime::Runtime; 24 | use tokio::sync::mpsc::{self, Sender}; 25 | use tokio::time::{self, Instant}; 26 | 27 | const TIMEOUT: Duration = Duration::from_secs(30); 28 | static RUNTIME: Lazy = Lazy::new(|| Runtime::new().unwrap()); 29 | 30 | #[derive(Debug, Error)] 31 | #[error(display = "No samples")] 32 | struct NoSamples; 33 | 34 | /// The TCP spammer. 35 | #[derive(StructOpt)] 36 | enum Command { 37 | /// Run in server mode. 38 | /// 39 | /// In server mode, it accepts connections, reads the whole content and sends its own content 40 | /// back, then closes the connection. 41 | Server { 42 | /// The address to listen on. 43 | #[structopt(short = "l", long = "listen", default_value = "0.0.0.0")] 44 | listen: IpAddr, 45 | /// The port to listen on. 46 | #[structopt(short = "p", long = "port", default_value = "2345")] 47 | port: u16, 48 | /// File with a content to send back. Nothing sent if missing. 49 | #[structopt(short = "c", long = "content", parse(from_os_str))] 50 | content: Option, 51 | /// Number of listen sockets and tasks. 52 | /// 53 | /// Raising this might be needed in high-performance scenarios. 54 | #[structopt(short = "a", long = "acceptors", default_value = "1")] 55 | acceptors: usize, 56 | }, 57 | /// Run in client mode, with provided rates to test with. 58 | /// 59 | /// Computes some statistics about latencies. 60 | Rate { 61 | /// The host to connect to. 62 | #[structopt(short = "h", long = "host", default_value = "127.0.0.1")] 63 | host: IpAddr, 64 | /// The port to connect to. 65 | #[structopt(short = "p", long = "port", default_value = "2345")] 66 | port: u16, 67 | /// The rate. 68 | /// 69 | /// Number of new connections started each second. Can be specified multiple times, in 70 | /// which case it'll run test with each. 71 | #[structopt(short = "r", long = "rate")] 72 | rate: Vec, 73 | /// The content to send to the server. Empty if missing. 74 | #[structopt(short = "c", long = "content", parse(from_os_str))] 75 | content: Option, 76 | /// Length of each test, in seconds. 77 | #[structopt(short = "l", long = "length", default_value = "10")] 78 | length: u32, 79 | /// Cooldown time (s) between tests, 80 | #[structopt(short = "o", long = "cooldown", default_value = "0")] 81 | cooldown: u64, 82 | /// Source IP to use. 83 | /// 84 | /// Can be used multiple times. If so, it is used in a round-robin fashion. If none 85 | /// provided, it is left up to the OS to decide. 86 | #[structopt(short = "I", long = "local-ip")] 87 | local_ips: Vec, 88 | }, 89 | /// Find the rate of connections at which the link saturates. 90 | /// 91 | /// Increases the rate of new connections in steps, until the last median is significantly 92 | /// worse than the previous step. 93 | Saturate { 94 | /// The host to connect to. 95 | #[structopt(short = "h", long = "host", default_value = "127.0.0.1")] 96 | host: IpAddr, 97 | /// The port to connect to. 98 | #[structopt(short = "p", long = "port", default_value = "2345")] 99 | port: u16, 100 | /// The rate to start at. 101 | #[structopt(short = "r", long = "start", default_value = "50")] 102 | start_rate: u32, 103 | /// Multiplication factor by which each step's rate is higher than the previous. 104 | #[structopt(short = "i", long = "increment", default_value = "1.25")] 105 | increment_factor: f64, 106 | /// A multiplication factor by which the current mean latency must be worse than the 107 | /// previous one to consider it a saturation. 108 | #[structopt(short = "s", long = "slowdown", default_value = "2")] 109 | slowdown_factor: f64, 110 | /// The content to send to the server. 111 | #[structopt(short = "c", long = "content", parse(from_os_str))] 112 | content: Option, 113 | /// Length of each test, in seconds. 114 | #[structopt(short = "l", long = "length", default_value = "10")] 115 | length: u32, 116 | /// A cooldown time (s) in between two tests. 117 | #[structopt(short = "o", long = "cooldown", default_value = "0")] 118 | cooldown: u64, 119 | /// Source IP to use. 120 | /// 121 | /// Can be used multiple times. If so, it is used in a round-robin fashion. If none 122 | /// provided, it is left up to the OS to decide. 123 | #[structopt(short = "I", long = "local-ip")] 124 | local_ips: Vec, 125 | }, 126 | } 127 | 128 | type Error = Box; 129 | 130 | fn get_content(content: Option) -> Result, Error> { 131 | Ok(content.map(fs::read).transpose()?.unwrap_or_default()) 132 | } 133 | 134 | struct Sink; 135 | 136 | impl AsyncWrite for Sink { 137 | fn poll_write( 138 | self: Pin<&mut Self>, 139 | _: &mut Context, 140 | buf: &[u8], 141 | ) -> Poll> { 142 | Poll::Ready(Ok(buf.len())) 143 | } 144 | fn poll_flush(self: Pin<&mut Self>, _: &mut Context) -> Poll> { 145 | Poll::Ready(Ok(())) 146 | } 147 | fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context) -> Poll> { 148 | Poll::Ready(Ok(())) 149 | } 150 | } 151 | 152 | async fn handle_conn_inner(mut connection: TcpStream, content: Arc<[u8]>) -> Result<(), Error> { 153 | io::copy(&mut connection, &mut Sink).await?; 154 | connection.write_all(&content).await?; 155 | Ok(()) 156 | } 157 | 158 | async fn handle_conn(addr: SocketAddr, connection: TcpStream, content: Arc<[u8]>) { 159 | if let Err(e) = handle_conn_inner(connection, content).await { 160 | error!("Error on connection {}: {}", addr, e); 161 | } 162 | } 163 | 164 | fn run_server(listen: IpAddr, port: u16, acceptors: usize, content: Vec) -> Result<(), Error> { 165 | let content: Arc<[u8]> = Arc::from(content); 166 | RUNTIME.block_on(async { 167 | for _ in 0..acceptors { 168 | let listener = match listen { 169 | IpAddr::V4(_) => TcpBuilder::new_v4()?, 170 | IpAddr::V6(_) => TcpBuilder::new_v6()?, 171 | }; 172 | if acceptors > 1 { 173 | listener.reuse_port(true)?; 174 | } 175 | let listener = listener 176 | .reuse_address(true)? 177 | .bind((listen, port))? 178 | .listen(20480)?; 179 | let listener = TcpListener::from_std(listener)?; 180 | let content = Arc::clone(&content); 181 | tokio::spawn(async move { 182 | loop { 183 | match listener.accept().await { 184 | Ok((connection, address)) => { 185 | debug!("Accepted connection from {}", address); 186 | tokio::spawn(handle_conn(address, connection, Arc::clone(&content))); 187 | } 188 | Err(e) => { 189 | warn!("Failed to accept connection: {}", e); 190 | } 191 | } 192 | } 193 | }); 194 | } 195 | future::pending().await 196 | }) 197 | } 198 | 199 | async fn connect_inner( 200 | server: SocketAddr, 201 | content: Arc<[u8]>, 202 | _ip: Option, 203 | ) -> Result { 204 | let start = Instant::now(); 205 | let mut connection = TcpStream::connect(server).await?; 206 | /* 207 | * FIXME: The async connect with bound local address doesn't seem to be available? :-( 208 | let mut connection = match ip { 209 | None => TcpStream::connect(server).await?, 210 | Some(IpAddr::V4(addr)) => { 211 | let stream = TcpBuilder::new_v4()?.bind((addr, 0))?.to_tcp_stream()?; 212 | TcpStream::connect_std(stream, &server, &Default::default()).await? 213 | } 214 | Some(IpAddr::V6(addr)) => { 215 | let stream = TcpBuilder::new_v6()?.bind((addr, 0))?.to_tcp_stream()?; 216 | TcpStream::connect_std(stream, &server, &Default::default()).await? 217 | } 218 | }; 219 | */ 220 | connection.write_all(&content).await?; 221 | connection.shutdown().await?; 222 | io::copy(&mut connection, &mut Sink).await?; 223 | Ok(start.elapsed()) 224 | } 225 | 226 | async fn connect( 227 | server: SocketAddr, 228 | content: Arc<[u8]>, 229 | ip: Option, 230 | results: Sender, 231 | ) { 232 | let connect = time::timeout(TIMEOUT, connect_inner(server, content, ip)); 233 | match connect.await { 234 | Ok(Ok(duration)) => results 235 | .send(duration) 236 | .await 237 | .expect("Channel prematurely closed"), 238 | Ok(Err(e)) => error!("Connection failed: {}", e), 239 | Err(_) => { 240 | warn!("Connection timed out"); 241 | results 242 | .send(TIMEOUT) 243 | .await 244 | .expect("Channel prematurely closed"); 245 | } 246 | } 247 | } 248 | 249 | async fn generator( 250 | server: SocketAddr, 251 | rate: u32, 252 | cnt: u32, 253 | content: Arc<[u8]>, 254 | local_ips: Arc<[IpAddr]>, 255 | results: Sender, 256 | ) { 257 | debug!("Generator started"); 258 | let interval = Duration::from_secs(1) / rate; 259 | let start = Instant::now(); 260 | for i in 0..cnt { 261 | time::sleep_until(start + i * interval).await; 262 | debug!("Starting connection #{}", i); 263 | let ip = if local_ips.is_empty() { 264 | None 265 | } else { 266 | local_ips.get((i as usize) % local_ips.len()).cloned() 267 | }; 268 | tokio::spawn(connect(server, Arc::clone(&content), ip, results.clone())); 269 | } 270 | debug!("Generator terminated"); 271 | } 272 | 273 | struct Latency { 274 | rate: u32, 275 | samples: usize, 276 | min: Duration, 277 | max: Duration, 278 | mean: Duration, 279 | p90: Duration, 280 | } 281 | 282 | impl Display for Latency { 283 | fn fmt(&self, fmt: &mut Formatter) -> FmtResult { 284 | write!( 285 | fmt, 286 | "Rate {} ({} samples):\tmin {:?},\tmax {:?},\tmean: {:?},\t90th: {:?}", 287 | self.rate, self.samples, self.min, self.max, self.mean, self.p90 288 | ) 289 | } 290 | } 291 | 292 | fn run_rate( 293 | server: SocketAddr, 294 | rate: u32, 295 | cnt: u32, 296 | content: Arc<[u8]>, 297 | local_ips: Arc<[IpAddr]>, 298 | ) -> Result, Error> { 299 | RUNTIME.block_on(async { 300 | let (sender, mut receiver) = mpsc::channel(10); 301 | tokio::spawn(generator(server, rate, cnt, content, local_ips, sender)); 302 | // TODO: Better computation of stats 303 | let mut results = Vec::new(); 304 | while let Some(duration) = receiver.recv().await { 305 | results.push(duration); 306 | } 307 | results.sort(); 308 | if results.is_empty() { 309 | Ok(Err(NoSamples)) 310 | } else { 311 | Ok(Ok(Latency { 312 | rate, 313 | samples: results.len(), 314 | min: results[0], 315 | max: results[results.len() - 1], 316 | mean: results[results.len() / 2], 317 | p90: results[results.len() * 9 / 10], 318 | })) 319 | } 320 | }) 321 | } 322 | 323 | fn run() -> Result<(), Error> { 324 | env_logger::init(); 325 | let command = Command::from_args(); 326 | match command { 327 | Command::Server { 328 | listen, 329 | port, 330 | content, 331 | acceptors, 332 | } => { 333 | info!("Starting server on port {}", port); 334 | run_server(listen, port, acceptors, get_content(content)?)?; 335 | } 336 | Command::Rate { 337 | host, 338 | port, 339 | rate, 340 | content, 341 | length, 342 | cooldown, 343 | local_ips, 344 | } => { 345 | let content = Arc::from(get_content(content)?); 346 | let local_ips = Arc::from(local_ips); 347 | let sockaddr = SocketAddr::new(host, port); 348 | let cooldown = Duration::from_secs(cooldown); 349 | let mut first = true; 350 | for rate in rate { 351 | if !mem::replace(&mut first, false) { 352 | thread::sleep(cooldown); 353 | } 354 | info!("Running client to {} with rate {}", sockaddr, rate); 355 | let start = Instant::now(); 356 | // TODO: Pauses in between 357 | match run_rate( 358 | sockaddr, 359 | rate, 360 | rate * length, 361 | Arc::clone(&content), 362 | Arc::clone(&local_ips), 363 | )? { 364 | Ok(rate) => println!("{}", rate), 365 | Err(NoSamples) => warn!("No samples for rate {}", rate), 366 | } 367 | info!("Step took {:?}", start.elapsed()); 368 | } 369 | } 370 | Command::Saturate { 371 | host, 372 | port, 373 | start_rate, 374 | increment_factor, 375 | slowdown_factor, 376 | content, 377 | length, 378 | cooldown, 379 | local_ips, 380 | } => { 381 | let content = Arc::from(get_content(content)?); 382 | let local_ips = Arc::from(local_ips); 383 | let sockaddr = SocketAddr::new(host, port); 384 | let cooldown = Duration::from_secs(cooldown); 385 | info!( 386 | "Running base client to {} with rate {}", 387 | sockaddr, start_rate 388 | ); 389 | let mut prev = run_rate( 390 | sockaddr, 391 | start_rate, 392 | start_rate * length, 393 | Arc::clone(&content), 394 | Arc::clone(&local_ips), 395 | )??; 396 | println!("Base {}", prev); 397 | for step in 1.. { 398 | let rate = (increment_factor.powi(step) * f64::from(start_rate)).round() as u32; 399 | thread::sleep(cooldown); 400 | info!("Running step {} with rate {}", step, rate); 401 | let start = Instant::now(); 402 | let lat = run_rate( 403 | sockaddr, 404 | rate, 405 | rate * length, 406 | Arc::clone(&content), 407 | Arc::clone(&local_ips), 408 | )??; 409 | info!("Step {} took {:?}", step, start.elapsed()); 410 | if lat.mean >= TIMEOUT 411 | || prev.mean.as_secs_f64() * slowdown_factor < lat.mean.as_secs_f64() 412 | { 413 | println!("Saturated at {}", lat); 414 | break; 415 | } else { 416 | println!("{}", lat); 417 | prev = lat; 418 | } 419 | } 420 | } 421 | } 422 | Ok(()) 423 | } 424 | 425 | fn main() { 426 | if let Err(e) = run() { 427 | error!("Fatal error: {}", e); 428 | process::exit(1); 429 | } 430 | } 431 | --------------------------------------------------------------------------------