Compare commits

...
11 changed files with 798 additions and 332 deletions

No files matched your search

Generated
+46 -311
View File
@@ -2,21 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
@@ -118,119 +103,24 @@ version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-channel"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-executor"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
dependencies = [
"async-channel 2.3.1",
"async-executor",
"async-io",
"async-lock",
"blocking",
"futures-lite",
"once_cell",
]
[[package]]
name = "async-io"
version = "2.4.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
dependencies = [
"async-lock",
"autocfg",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite",
"parking",
"polling",
"rustix 0.38.44",
"rustix",
"slab",
"tracing",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener 5.4.0",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-std"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b"
dependencies = [
"async-channel 1.9.0",
"async-global-executor",
"async-io",
"async-lock",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
version = "0.1.86"
@@ -242,33 +132,12 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
name = "base64"
version = "0.21.7"
@@ -281,19 +150,6 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "blocking"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
dependencies = [
"async-channel 2.3.1",
"async-task",
"futures-io",
"futures-lite",
"piper",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
@@ -629,39 +485,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2"
dependencies = [
"event-listener 5.4.0",
"pin-project-lite",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "foldhash"
version = "0.1.5"
@@ -727,14 +550,11 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
version = "2.6.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"parking",
"pin-project-lite",
]
@@ -810,24 +630,6 @@ dependencies = [
"wasip3",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "gloo-timers"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "half"
version = "2.4.1"
@@ -876,9 +678,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.4.0"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
[[package]]
name = "hickory-proto"
@@ -1114,7 +916,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [
"socket2",
"socket2 0.5.8",
"widestring",
"windows-sys 0.48.0",
"winreg",
@@ -1190,15 +992,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
@@ -1207,9 +1000,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.181"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libredox"
@@ -1227,12 +1020,6 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
@@ -1260,9 +1047,6 @@ name = "log"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
dependencies = [
"value-bag",
]
[[package]]
name = "lru-cache"
@@ -1285,24 +1069,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.3"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
dependencies = [
"libc",
"wasi",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -1333,15 +1108,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -1438,17 +1204,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [
"atomic-waker",
"fastrand",
"futures-io",
]
[[package]]
name = "plotters"
version = "0.3.7"
@@ -1479,17 +1234,16 @@ dependencies = [
[[package]]
name = "polling"
version = "3.7.4"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix 0.38.44",
"tracing",
"windows-sys 0.59.0",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
@@ -1705,25 +1459,6 @@ dependencies = [
"libc",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.1.3"
@@ -1733,7 +1468,7 @@ dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.11.0",
"linux-raw-sys",
"windows-sys 0.60.2",
]
@@ -1775,7 +1510,7 @@ dependencies = [
"ansi_term",
"anstream",
"anyhow",
"async-std",
"async-io",
"cidr-utils",
"clap",
"colored",
@@ -1795,6 +1530,7 @@ dependencies = [
"serde",
"serde_derive",
"text_placeholder",
"tokio",
"toml",
]
@@ -1923,6 +1659,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@@ -1974,7 +1720,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
dependencies = [
"rustix 1.1.3",
"rustix",
"windows-sys 0.60.2",
]
@@ -2066,17 +1812,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.2"
version = "1.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"pin-project-lite",
"socket2",
"windows-sys 0.52.0",
"socket2 0.6.5",
"windows-sys 0.61.2",
]
[[package]]
@@ -2206,12 +1951,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "value-bag"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0"
[[package]]
name = "version_check"
version = "0.9.5"
@@ -2278,19 +2017,6 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
@@ -2446,6 +2172,15 @@ dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
+3 -1
View File
@@ -19,8 +19,9 @@ exclude = [
[dependencies]
clap = { version = "4.6.1", features = ["derive", "wrap_help"] }
colored = "3.1.1"
async-std = "1.13.2"
tokio = { version = "1.53.1", features = ["rt", "net", "time"] }
futures = "0.3"
async-io = "2.4.0"
rlimit = "0.11.0"
log = "0.4.32"
env_logger = "0.11.10"
@@ -41,6 +42,7 @@ text_placeholder = { version = "0.5", features = ["struct_context"] }
once_cell = "1.21.4"
[dev-dependencies]
tokio = { version = "1.53.1", features = ["test-util"] }
parameterized = "2.0.0"
criterion = { version = "0.8", features = ["html_reports"] }
+51
View File
@@ -1,13 +1,19 @@
use criterion::{criterion_group, criterion_main, Criterion};
use futures::{stream::FuturesUnordered, StreamExt};
use rustscan::generated::get_parsed_data;
use rustscan::input::{Opts, PortRanges, ScanOrder};
use rustscan::port_strategy::PortStrategy;
use rustscan::scanner::build_udp_payload_lookup;
use std::collections::BTreeMap;
use std::hint::black_box;
use std::io;
use std::net::IpAddr;
use std::time::Duration;
// Benchmark the production timeout path with simulated I/O operations.
#[path = "../src/scanner/timeout.rs"]
mod timeout;
fn bench_address() {
let _addrs = ["127.0.0.1".parse::<IpAddr>().unwrap()];
}
@@ -47,7 +53,52 @@ fn old_payload_for_port(udp_map: &'static BTreeMap<Vec<u16>, Vec<u8>>, port: u16
payload
}
// Exercise the scanner's bounded scheduling pattern without opening sockets.
// Ready operations model immediate replies; pending operations expire instead.
async fn simulated_operation(expires: bool) -> io::Result<()> {
timeout::io_timeout(Duration::from_millis(10), async {
if expires {
std::future::pending::<()>().await;
}
Ok(())
})
.await
}
async fn simulated_batch(expires: bool) {
const OPERATIONS: usize = 4096;
const BATCH_SIZE: usize = 256;
let mut pending = FuturesUnordered::new();
for _ in 0..BATCH_SIZE {
pending.push(simulated_operation(expires));
}
let mut remaining = OPERATIONS - BATCH_SIZE;
while let Some(result) = pending.next().await {
let _ = black_box(result);
if remaining > 0 {
pending.push(simulated_operation(expires));
remaining -= 1;
}
}
}
fn criterion_benchmark(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let mut runtime_group = c.benchmark_group("runtime scheduling");
runtime_group.sample_size(20);
runtime_group.warm_up_time(Duration::from_secs(1));
runtime_group.measurement_time(Duration::from_secs(5));
runtime_group.bench_function("ready operations", |b| {
b.iter(|| runtime.block_on(simulated_batch(false)));
});
runtime_group.bench_function("expired operations", |b| {
b.iter(|| runtime.block_on(simulated_batch(true)));
});
runtime_group.finish();
// Benching helper functions
c.bench_function("parse address", |b| b.iter(bench_address));
+49
View File
@@ -0,0 +1,49 @@
--- a/benches/benchmark_helpers.rs
+++ b/benches/benchmark_helpers.rs
@@ -1,0 +2 @@
+use futures::{stream::FuturesUnordered, StreamExt};
@@ -7,0 +9 @@
+use std::io;
@@ -49,0 +52,29 @@
+// Exercise the scanner's bounded scheduling pattern without opening sockets.
+// Ready operations model immediate replies; pending operations expire instead.
+async fn simulated_operation(expires: bool) -> io::Result<()> {
+ async_std::io::timeout(Duration::from_millis(10), async {
+ if expires {
+ std::future::pending::<()>().await;
+ }
+ Ok(())
+ })
+ .await
+}
+
+async fn simulated_batch(expires: bool) {
+ const OPERATIONS: usize = 4096;
+ const BATCH_SIZE: usize = 256;
+ let mut pending = FuturesUnordered::new();
+ for _ in 0..BATCH_SIZE {
+ pending.push(simulated_operation(expires));
+ }
+ let mut remaining = OPERATIONS - BATCH_SIZE;
+ while let Some(result) = pending.next().await {
+ let _ = black_box(result);
+ if remaining > 0 {
+ pending.push(simulated_operation(expires));
+ remaining -= 1;
+ }
+ }
+}
+
@@ -50,0 +82,12 @@
+ let mut runtime_group = c.benchmark_group("runtime scheduling");
+ runtime_group.sample_size(20);
+ runtime_group.warm_up_time(Duration::from_secs(1));
+ runtime_group.measurement_time(Duration::from_secs(5));
+ runtime_group.bench_function("ready operations", |b| {
+ b.iter(|| futures::executor::block_on(simulated_batch(false)));
+ });
+ runtime_group.bench_function("expired operations", |b| {
+ b.iter(|| futures::executor::block_on(simulated_batch(true)));
+ });
+ runtime_group.finish();
+
+25
View File
@@ -0,0 +1,25 @@
{"metadata": {"base": "cfb864590161d1c8a003811dcf4846ceb8c8284f", "head": "42427e88c1a6587d0a8b198a73bac4553bb4ad72", "kernel": "7.2.6-1-cachyos", "scanner_cpus": "0-3", "server_cpus": [4, 5, 6, 7], "trials": 12, "warmups": 2, "binary_sha256": {"async-std": "00c7f0d91bab260ac622540e86083175afacfd1677647d2b8e96e56ba4f60732", "tokio": "669627834d8a32f60e2167e78a9518dcede7562831adb85fb61a9d94871494b8"}, "date": "2026-10-01", "cpu": "Intel Core Ultra 7 165U", "rustc": "1.98.1", "rlimit_nofile_soft": 65536, "server_observed_counts": {"tcp": 58702, "udp_echo": 14336, "udp_silent": 17920}, "firewall_dropped_tcp_packets": 21504, "sample_order": "First two entries per series are warmups; remaining twelve are measured runs. Runtime execution order alternates for each pair.", "build_profile": "release with LTO", "network": "private namespace; loopback only"},
"cases": [
{"case": "tcp_closed_65535_b256", "arguments": ["--addresses", "127.0.0.2", "--range", "1-65535", "--batch-size", "256", "--timeout", "1500", "--tries", "1"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "tcp_closed_65535_b256", "async-std": {"n": 12, "scan_median_ms": 565.61545, "scan_min_ms": 542.8159, "scan_max_ms": 597.2202, "wall_median_ms": 678.4058859998368}, "tokio": {"n": 12, "scan_median_ms": 377.77217499999995, "scan_min_ms": 365.76328, "scan_max_ms": 391.70304, "wall_median_ms": 490.91583049948895}, "paired_ratio_median": 0.6691842486934787, "paired_ratio_bootstrap_95_percent": [0.651478199869526, 0.680316018080831]}, "samples": {"async-std": {"scan_ms": [547.71084, 546.87524, 573.7815, 542.8159, 557.4494, 551.8315, 552.46323, 547.43415, 579.65297, 576.02394, 577.4498, 548.5023, 580.08724, 597.2202], "wall_ms": [663.019741, 658.9046, 687.255539, 653.559305, 669.556233, 660.832612, 664.688112, 665.16539, 692.104642, 692.894896, 699.378062, 663.848877, 693.821868, 715.319005]}, "tokio": {"scan_ms": [386.10637, 366.50577, 388.7874, 388.9165, 367.81788, 391.70304, 375.97436, 365.76328, 379.11865, 386.067, 374.6031, 373.03102, 376.4257, 384.8664], "wall_ms": [498.365617, 480.96794, 508.885514, 507.263527, 481.161202, 502.921307, 490.034017, 479.758393, 491.797644, 497.79691, 489.335471, 487.71927, 489.31152, 502.222538]}}},
{"case": "tcp_closed_65535_b4500", "arguments": ["--addresses", "127.0.0.2", "--range", "1-65535", "--batch-size", "4500", "--timeout", "1500", "--tries", "1"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "tcp_closed_65535_b4500", "async-std": {"n": 12, "scan_median_ms": 769.2585799999999, "scan_min_ms": 751.53786, "scan_max_ms": 794.6415, "wall_median_ms": 886.4860560001944}, "tokio": {"n": 12, "scan_median_ms": 571.338355, "scan_min_ms": 554.6383000000001, "scan_max_ms": 612.74534, "wall_median_ms": 686.1564379996707}, "paired_ratio_median": 0.7392118097843652, "paired_ratio_bootstrap_95_percent": [0.7270143421681834, 0.7653997249648308]}, "samples": {"async-std": {"scan_ms": [775.69896, 781.10725, 767.2349, 769.74416, 755.10293, 775.59453, 783.22995, 767.6211, 776.608, 751.53786, 752.1119, 779.8275, 768.773, 794.6415], "wall_ms": [900.142351, 901.448905, 882.951503, 885.785811, 871.963026, 891.82683, 895.949848, 883.562564, 891.482204, 863.927796, 866.721748, 894.248947, 887.186301, 909.792057]}, "tokio": {"scan_ms": [579.2819, 566.00755, 582.7605, 559.8762, 566.8316, 554.6383, 576.05165, 563.19356, 612.74534, 586.5669, 580.0588, 566.6812, 571.15334, 571.52337], "wall_ms": [692.862157, 680.198058, 698.352173, 671.65832, 680.533892, 668.544743, 688.044384, 674.552564, 735.085916, 699.754035, 695.691597, 680.953486, 687.596672, 684.716204]}}},
{"case": "tcp_mixed_4096_b256", "arguments": ["--addresses", "127.0.0.1", "--range", "20000-24095", "--batch-size", "256", "--timeout", "1500", "--tries", "1"], "expected_open_count": 512, "expected_closed_count": 0, "summary": {"case": "tcp_mixed_4096_b256", "async-std": {"n": 12, "scan_median_ms": 68.141467, "scan_min_ms": 55.01162, "scan_max_ms": 74.22786, "wall_median_ms": 187.83275400073762}, "tokio": {"n": 12, "scan_median_ms": 27.07619, "scan_min_ms": 26.30396, "scan_max_ms": 29.205604, "wall_median_ms": 141.96964999973716}, "paired_ratio_median": 0.3978116368627497, "paired_ratio_bootstrap_95_percent": [0.37823381704207826, 0.43222201838556734]}, "samples": {"async-std": {"scan_ms": [63.719936, 66.61555, 55.01162, 70.96551, 73.14818, 73.35321, 63.210845, 67.237474, 63.8246, 74.22786, 72.827265, 69.04546, 66.88673, 56.79349], "wall_ms": [180.925745, 189.033784, 169.724796, 182.401319, 193.146419, 198.574615, 187.32776, 187.759146, 189.552171, 193.552151, 187.906362, 186.211485, 188.919732, 170.810584]}, "tokio": {"scan_ms": [26.11342, 28.618133, 26.30396, 27.60356, 26.624788, 26.957022, 27.126616, 27.040524, 27.782789, 29.205604, 26.756745, 27.10681, 27.859924, 27.04557], "wall_ms": [142.457495, 146.887664, 141.370764, 145.212753, 139.031326, 141.839596, 142.860691, 141.604121, 142.410272, 144.175928, 141.592621, 140.95038, 142.099704, 142.563949]}}},
{"case": "tcp_mixed_4096_b4500", "arguments": ["--addresses", "127.0.0.1", "--range", "20000-24095", "--batch-size", "4500", "--timeout", "1500", "--tries", "1"], "expected_open_count": 512, "expected_closed_count": 0, "summary": {"case": "tcp_mixed_4096_b4500", "async-std": {"n": 12, "scan_median_ms": 105.183213, "scan_min_ms": 92.38623, "scan_max_ms": 118.32085000000001, "wall_median_ms": 223.5699919997387}, "tokio": {"n": 12, "scan_median_ms": 40.7245485, "scan_min_ms": 40.203160000000004, "scan_max_ms": 43.439656, "wall_median_ms": 156.73000700007833}, "paired_ratio_median": 0.4002557620046163, "paired_ratio_bootstrap_95_percent": [0.37097988370736024, 0.41671607494471485]}, "samples": {"async-std": {"scan_ms": [115.14703, 114.446916, 92.38623, 108.52898, 107.40388, 102.962546, 110.30818, 101.29879, 110.973485, 99.98305, 96.58376, 93.3442, 118.32085, 109.31534], "wall_ms": [237.680642, 228.93913, 214.413981, 229.134308, 220.8739, 224.930336, 225.133944, 224.350373, 224.012733, 218.937123, 211.773245, 213.402572, 230.26942, 223.127251]}, "tokio": {"scan_ms": [42.504027, 40.87808, 42.628516, 40.42182, 43.439656, 41.02149, 40.5159, 41.792054, 41.588504, 40.20316, 40.6492, 40.36123, 40.799897, 40.392883], "wall_ms": [164.520424, 153.958011, 168.511547, 151.627343, 157.813913, 158.826988, 155.48564, 159.408084, 159.302398, 157.162484, 155.533542, 153.505071, 156.29753, 155.263059]}}},
{"case": "tcp_open_512_b4500", "arguments": ["--addresses", "127.0.0.1", "--range", "20000-20511", "--batch-size", "4500", "--timeout", "1500", "--tries", "1"], "expected_open_count": 512, "expected_closed_count": 0, "summary": {"case": "tcp_open_512_b4500", "async-std": {"n": 12, "scan_median_ms": 47.70304, "scan_min_ms": 36.67692, "scan_max_ms": 68.96059, "wall_median_ms": 160.65413699971032}, "tokio": {"n": 12, "scan_median_ms": 6.261838, "scan_min_ms": 6.104236, "scan_max_ms": 6.608183, "wall_median_ms": 124.33755050005857}, "paired_ratio_median": 0.13084120838120605, "paired_ratio_bootstrap_95_percent": [0.11244047830149623, 0.1483106271556775]}, "samples": {"async-std": {"scan_ms": [46.98071, 49.201056, 46.035964, 58.204245, 68.96059, 45.698512, 38.846433, 56.73356, 46.20831, 36.67692, 41.455615, 49.29091, 49.19777, 57.64334], "wall_ms": [160.558191, 160.740311, 158.833574, 170.663018, 181.746459, 157.622693, 152.247554, 167.369295, 158.456413, 148.534519, 155.633953, 162.4747, 167.594306, 173.535561]}, "tokio": {"scan_ms": [6.28506, 6.0532, 6.104236, 6.147001, 6.206233, 6.270633, 6.42198, 6.306521, 6.194992, 6.253043, 6.608183, 6.158266, 6.350709, 6.555237], "wall_ms": [125.899195, 127.460307, 125.087796, 116.874444, 118.344468, 117.786915, 125.684343, 129.993995, 123.587305, 127.177718, 128.525335, 130.741019, 116.031822, 120.737224]}}},
{"case": "tcp_ipv6_mixed_4096_b4500", "arguments": ["--addresses", "::1", "--range", "20000-24095", "--batch-size", "4500", "--timeout", "1500", "--tries", "1"], "expected_open_count": 512, "expected_closed_count": 0, "summary": {"case": "tcp_ipv6_mixed_4096_b4500", "async-std": {"n": 12, "scan_median_ms": 106.25599, "scan_min_ms": 92.231296, "scan_max_ms": 119.37266000000001, "wall_median_ms": 225.6745664999471}, "tokio": {"n": 12, "scan_median_ms": 40.983215, "scan_min_ms": 40.500764000000004, "scan_max_ms": 43.351088000000004, "wall_median_ms": 154.43425250032305}, "paired_ratio_median": 0.38505220824311115, "paired_ratio_bootstrap_95_percent": [0.36158756791142943, 0.39815014470090115]}, "samples": {"async-std": {"scan_ms": [103.4242, 101.70391, 113.68274, 102.45315, 105.20947, 100.802965, 105.56823, 113.60494, 116.37136, 106.94375, 119.37266, 112.66027, 104.448915, 92.231296], "wall_ms": [227.238681, 213.929011, 228.832603, 223.209492, 218.101656, 211.377824, 221.963913, 228.139641, 230.733897, 221.140289, 232.238324, 230.332511, 228.886917, 212.248323]}, "tokio": {"scan_ms": [41.84887, 42.669304, 43.351088, 41.374706, 40.902335, 42.38061, 41.326944, 40.948737, 40.565256, 40.500764, 41.106615, 40.864874, 40.992025, 40.974405], "wall_ms": [154.656851, 162.111319, 164.86059, 152.724654, 155.535032, 161.052929, 153.618886, 154.69096, 152.754894, 154.585249, 154.283256, 153.658405, 153.15487, 155.942636]}}},
{"case": "udp_echo_256_b256", "arguments": ["--addresses", "127.0.0.1", "--range", "30000-30255", "--batch-size", "256", "--timeout", "1500", "--tries", "1", "--udp"], "expected_open_count": 256, "expected_closed_count": 0, "summary": {"case": "udp_echo_256_b256", "async-std": {"n": 12, "scan_median_ms": 26.09995, "scan_min_ms": 16.339796, "scan_max_ms": 51.66373, "wall_median_ms": 144.53015349999987}, "tokio": {"n": 12, "scan_median_ms": 3.550261, "scan_min_ms": 3.4090409999999998, "scan_max_ms": 3.760615, "wall_median_ms": 117.10653199997978}, "paired_ratio_median": 0.13508511965414755, "paired_ratio_bootstrap_95_percent": [0.10720311129055016, 0.16944309058040086]}, "samples": {"async-std": {"scan_ms": [21.703886, 37.568197, 16.339796, 19.275581, 25.42451, 26.131544, 26.231969, 26.068356, 40.599234, 21.879036, 51.66373, 29.525625, 37.230626, 20.666992], "wall_ms": [132.136823, 160.093568, 127.916215, 136.567585, 139.767169, 145.03025, 147.152265, 144.030057, 152.23601, 140.657323, 174.678649, 151.764345, 157.714272, 138.801602]}, "tokio": {"scan_ms": [3.58793, 3.647557, 3.512101, 3.733033, 3.409041, 3.46458, 3.760615, 3.547523, 3.589418, 3.47494, 3.5728, 3.552999, 3.502289, 3.721321], "wall_ms": [116.999981, 123.901236, 115.435224, 118.95129, 116.437523, 116.181437, 113.989477, 117.653641, 119.460373, 116.987976, 119.387785, 118.771634, 112.816753, 117.225088]}}},
{"case": "udp_ipv6_echo_256_b256", "arguments": ["--addresses", "::1", "--range", "30000-30255", "--batch-size", "256", "--timeout", "1500", "--tries", "1", "--udp"], "expected_open_count": 256, "expected_closed_count": 0, "summary": {"case": "udp_ipv6_echo_256_b256", "async-std": {"n": 12, "scan_median_ms": 27.625454499999996, "scan_min_ms": 19.875253999999998, "scan_max_ms": 38.867235, "wall_median_ms": 145.4195784995136}, "tokio": {"n": 12, "scan_median_ms": 3.799017, "scan_min_ms": 3.6955, "scan_max_ms": 4.067423, "wall_median_ms": 119.61354899995058}, "paired_ratio_median": 0.13852945122327148, "paired_ratio_bootstrap_95_percent": [0.11635290629059791, 0.15096773740372016]}, "samples": {"async-std": {"scan_ms": [40.33954, 21.779982, 25.549388, 33.147827, 25.456525, 26.251681, 28.999228, 38.867235, 36.46853, 19.875254, 32.311674, 25.559943, 29.844549, 24.700988], "wall_ms": [163.080692, 139.558642, 147.557955, 145.782568, 145.197701, 140.07066, 148.222519, 152.220419, 156.648518, 130.942647, 145.126391, 137.323428, 141.182265, 145.641456]}, "tokio": {"scan_ms": [3.874475, 3.758481, 3.860633, 3.877691, 3.839627, 3.6955, 3.873471, 3.699186, 3.83033, 3.767704, 3.739238, 3.763777, 4.067423, 3.736082], "wall_ms": [116.249398, 116.498595, 115.169205, 119.516884, 123.086973, 115.31348, 123.611914, 113.79081, 114.133146, 120.625814, 119.710214, 126.404842, 121.332105, 115.011208]}}},
{"case": "tcp_filtered_128_t20_retry2_b64", "arguments": ["--addresses", "127.0.0.3", "--range", "40000-40127", "--batch-size", "64", "--timeout", "20", "--tries", "2"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "tcp_filtered_128_t20_retry2_b64", "async-std": {"n": 12, "scan_median_ms": 87.6446925, "scan_min_ms": 83.705105, "scan_max_ms": 95.73294, "wall_median_ms": 203.2048140004008}, "tokio": {"n": 12, "scan_median_ms": 90.200395, "scan_min_ms": 84.00111, "scan_max_ms": 99.7434, "wall_median_ms": 203.24993450003603}, "paired_ratio_median": 1.0119900466846206, "paired_ratio_bootstrap_95_percent": [0.9963617580190984, 1.0629572999350771]}, "samples": {"async-std": {"scan_ms": [87.98372, 91.602646, 86.6592, 92.56119, 83.82087, 83.89964, 91.62219, 83.705105, 92.42998, 88.630185, 85.04144, 95.73294, 90.631686, 86.57442], "wall_ms": [209.289809, 203.418129, 204.967734, 208.14526, 201.807086, 200.596062, 209.949818, 200.093062, 210.158309, 204.013388, 200.23668, 204.49939, 202.39624, 197.121998]}, "tokio": {"scan_ms": [83.77635, 87.813005, 90.50424, 93.56978, 89.589715, 93.88123, 84.00111, 84.11684, 93.56413, 89.66802, 89.89655, 93.36173, 89.5264, 99.7434], "wall_ms": [194.068953, 202.098137, 203.901289, 205.50657, 199.834834, 204.891561, 197.104592, 195.919982, 202.59858, 200.310774, 199.606024, 210.950254, 205.862549, 208.864636]}}},
{"case": "tcp_filtered_128_t1500_b4500", "arguments": ["--addresses", "127.0.0.3", "--range", "40000-40127", "--batch-size", "4500", "--timeout", "1500", "--tries", "1"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "tcp_filtered_128_t1500_b4500", "async-std": {"n": 12, "scan_median_ms": 1519.9909000000002, "scan_min_ms": 1514.0051, "scan_max_ms": 1527.4003, "wall_median_ms": 1625.5414565002866}, "tokio": {"n": 12, "scan_median_ms": 1500.6792500000001, "scan_min_ms": 1500.6533, "scan_max_ms": 1500.7889, "wall_median_ms": 1607.9048350006815}, "paired_ratio_median": 0.9872864815881686, "paired_ratio_bootstrap_95_percent": [0.9860211048523806, 0.9893007737644279]}, "samples": {"async-std": {"scan_ms": [1520.2672, 1516.4726, 1514.8814, 1521.4541, 1526.0279, 1521.2376, 1516.8859, 1514.0051, 1517.0343, 1517.0065, 1527.4003, 1521.6393, 1522.2969, 1518.7442], "wall_ms": [1629.725436, 1624.468006, 1623.793379, 1628.174016, 1633.755173, 1633.153861, 1623.867637, 1620.035128, 1623.419386, 1623.095493, 1633.97451, 1627.215276, 1628.552662, 1623.647248]}, "tokio": {"scan_ms": [1500.7088, 1500.7062, 1500.6834, 1500.7012, 1500.6533, 1500.6555, 1500.6707, 1500.6621, 1500.7889, 1500.6878, 1500.6776, 1500.6809, 1500.7043, 1500.6754], "wall_ms": [1610.089424, 1608.901673, 1607.865974, 1607.1767, 1609.037891, 1613.202192, 1607.943696, 1605.800055, 1608.702953, 1610.989485, 1606.903005, 1606.369366, 1607.172129, 1610.257635]}}},
{"case": "udp_silent_128_t20_retry2_b64", "arguments": ["--addresses", "127.0.0.1", "--range", "31000-31127", "--batch-size", "64", "--timeout", "20", "--tries", "2", "--udp"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "udp_silent_128_t20_retry2_b64", "async-std": {"n": 12, "scan_median_ms": 91.0113, "scan_min_ms": 85.47477, "scan_max_ms": 101.52072500000001, "wall_median_ms": 198.82166349998442}, "tokio": {"n": 12, "scan_median_ms": 82.374075, "scan_min_ms": 82.15854999999999, "scan_max_ms": 82.71911999999999, "wall_median_ms": 193.47493999975995}, "paired_ratio_median": 0.9056145986510931, "paired_ratio_bootstrap_95_percent": [0.8684654796153525, 0.9399090634650837]}, "samples": {"async-std": {"scan_ms": [99.2002, 87.0283, 92.82178, 93.114525, 96.1305, 89.20082, 87.134354, 86.520605, 88.210866, 85.47477, 101.520725, 93.399614, 97.3816, 88.98857], "wall_ms": [206.826011, 195.030786, 201.546836, 202.165958, 203.125042, 195.259398, 194.754565, 194.000578, 195.239916, 193.140741, 209.96905, 200.410742, 205.804614, 197.232585]}, "tokio": {"scan_ms": [82.729965, 82.53965, 82.33637, 82.18716, 82.41128, 82.5791, 82.33687, 82.54546, 82.466274, 82.26455, 82.71912, 82.15855, 82.562424, 82.242526], "wall_ms": [192.803999, 192.725638, 193.896498, 192.528066, 194.065959, 195.141017, 191.801711, 191.701865, 192.781206, 194.752557, 195.949918, 194.693378, 193.053382, 189.984702]}}},
{"case": "udp_silent_128_t1500_b4500", "arguments": ["--addresses", "127.0.0.1", "--range", "31000-31127", "--batch-size", "4500", "--timeout", "1500", "--tries", "1", "--udp"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "udp_silent_128_t1500_b4500", "async-std": {"n": 12, "scan_median_ms": 1518.52745, "scan_min_ms": 1514.0991, "scan_max_ms": 1528.4443, "wall_median_ms": 1626.3375505000113}, "tokio": {"n": 12, "scan_median_ms": 1501.6571000000001, "scan_min_ms": 1501.6054000000001, "scan_max_ms": 1501.781, "wall_median_ms": 1608.1746704999205}, "paired_ratio_median": 0.9888991810266266, "paired_ratio_bootstrap_95_percent": [0.9865208648810749, 0.9898526070883118]}, "samples": {"async-std": {"scan_ms": [1518.4212, 1517.0245, 1518.4689, 1516.9675, 1520.9479, 1523.4239, 1518.586, 1514.0991, 1515.2898, 1517.1113, 1526.8329, 1520.123, 1517.5409, 1528.4443], "wall_ms": [1625.755639, 1625.149282, 1623.676844, 1629.639593, 1629.579045, 1629.622137, 1625.341947, 1622.234258, 1621.903718, 1622.809251, 1632.650784, 1627.333154, 1623.842187, 1634.457294]}, "tokio": {"scan_ms": [1501.6277, 1501.7376, 1501.656, 1501.6828, 1501.6761, 1501.6582, 1501.6851, 1501.6054, 1501.781, 1501.608, 1501.6263, 1501.6633, 1501.641, 1501.6465], "wall_ms": [1609.273123, 1608.924992, 1609.919092, 1607.439902, 1608.738686, 1609.290304, 1607.610655, 1606.815587, 1606.94348, 1606.421318, 1610.999882, 1608.945001, 1608.793034, 1607.310885]}}},
{"case": "tcp_open_16_interval5", "arguments": ["--addresses", "127.0.0.1", "--range", "20000-20015", "--batch-size", "256", "--timeout", "1500", "--tries", "1", "--interval", "5"], "expected_open_count": 16, "expected_closed_count": 0, "summary": {"case": "tcp_open_16_interval5", "async-std": {"n": 12, "scan_median_ms": 76.526898, "scan_min_ms": 76.28906, "scan_max_ms": 76.98857000000001, "wall_median_ms": 184.3007565003063}, "tokio": {"n": 12, "scan_median_ms": 76.198385, "scan_min_ms": 75.96929, "scan_max_ms": 76.34483, "wall_median_ms": 182.87123099980818}, "paired_ratio_median": 0.995816455071374, "paired_ratio_bootstrap_95_percent": [0.9940940322939229, 0.997153398744211]}, "samples": {"async-std": {"scan_ms": [76.22546, 76.634064, 76.59302, 76.39457, 76.645374, 76.3332, 76.62176, 76.28906, 76.41959, 76.36926, 76.4935, 76.560296, 76.98857, 76.76206], "wall_ms": [184.669709, 182.866488, 185.538649, 183.313847, 184.925599, 183.675914, 183.637934, 185.093184, 187.063112, 183.638293, 182.9653, 182.843469, 186.901708, 185.251247]}, "tokio": {"scan_ms": [76.195486, 75.97554, 76.34483, 76.11899, 76.28056, 76.30077, 76.1682, 76.29438, 75.96929, 76.17687, 76.25071, 76.16523, 76.15225, 76.2199], "wall_ms": [184.722406, 182.572365, 187.34923, 182.041604, 183.36494, 182.523404, 183.084177, 182.781318, 182.961144, 182.379863, 182.473341, 182.171679, 187.598076, 184.172442]}}},
{"case": "tcp_filtered_128_t1_b1", "arguments": ["--addresses", "127.0.0.3", "--range", "40000-40127", "--batch-size", "1", "--timeout", "1", "--tries", "1"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "tcp_filtered_128_t1_b1", "async-std": {"n": 12, "scan_median_ms": 130.93322, "scan_min_ms": 130.62957, "scan_max_ms": 131.24298000000002, "wall_median_ms": 239.21898499975214}, "tokio": {"n": 12, "scan_median_ms": 131.0992, "scan_min_ms": 130.88854, "scan_max_ms": 131.27832, "wall_median_ms": 239.95067800024117}, "paired_ratio_median": 1.0005112769246107, "paired_ratio_bootstrap_95_percent": [0.9999663972630607, 1.0021629287391751]}, "samples": {"async-std": {"scan_ms": [130.78079, 131.10988, 130.9561, 130.94509, 130.83921, 131.1865, 130.62957, 131.02207, 130.70644, 130.90175, 130.75319, 131.24298, 130.92135, 131.06887], "wall_ms": [239.781417, 238.586222, 239.454156, 237.931582, 238.284514, 238.520237, 241.34305, 238.878925, 240.314313, 240.056949, 238.664271, 241.762701, 238.983814, 241.307165]}, "tokio": {"scan_ms": [130.6635, 130.95422, 130.92089, 130.88854, 130.99842, 131.1627, 131.0357, 131.17874, 131.2091, 130.93698, 131.27832, 131.25797, 130.9423, 131.16762], "wall_ms": [241.110604, 238.237406, 241.265624, 236.701241, 243.135758, 242.763681, 240.139445, 237.299031, 243.166426, 239.761911, 238.337466, 242.007046, 237.278855, 238.046974]}}},
{"case": "tcp_filtered_128_t5_b1", "arguments": ["--addresses", "127.0.0.3", "--range", "40000-40127", "--batch-size", "1", "--timeout", "5", "--tries", "1"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "tcp_filtered_128_t5_b1", "async-std": {"n": 12, "scan_median_ms": 644.5146500000001, "scan_min_ms": 644.1285, "scan_max_ms": 645.423, "wall_median_ms": 753.1218705003084}, "tokio": {"n": 12, "scan_median_ms": 644.9479249999999, "scan_min_ms": 644.26196, "scan_max_ms": 645.4041000000001, "wall_median_ms": 753.6899540000377}, "paired_ratio_median": 1.0003472826276902, "paired_ratio_bootstrap_95_percent": [0.9997139996239722, 1.0010076863466117]}, "samples": {"async-std": {"scan_ms": [644.68694, 644.6084, 644.4459, 644.1285, 644.51116, 644.7121, 644.44476, 644.48434, 644.4745, 645.2324, 644.8094, 645.423, 644.8345, 644.51814], "wall_ms": [752.617209, 752.072663, 751.723478, 753.035024, 754.164624, 753.567375, 754.457316, 751.579595, 751.879974, 753.208717, 756.413493, 755.272146, 751.549782, 750.185216]}, "tokio": {"scan_ms": [644.9864, 644.3036, 644.2817, 644.75507, 644.70977, 644.51116, 644.26196, 645.1563, 645.34944, 645.04635, 645.124, 644.8495, 645.08367, 645.4041], "wall_ms": [750.909824, 751.864968, 752.487215, 754.880392, 752.804057, 752.172361, 751.845615, 752.622455, 754.163878, 759.625524, 753.21603, 761.649699, 759.674361, 759.360811]}}},
{"case": "udp_silent_128_t1_b1", "arguments": ["--addresses", "127.0.0.1", "--range", "31000-31127", "--batch-size", "1", "--timeout", "1", "--tries", "1", "--udp"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "udp_silent_128_t1_b1", "async-std": {"n": 12, "scan_median_ms": 133.377255, "scan_min_ms": 132.9543, "scan_max_ms": 134.05539, "wall_median_ms": 241.22244349973698}, "tokio": {"n": 12, "scan_median_ms": 133.435925, "scan_min_ms": 132.90279999999998, "scan_max_ms": 133.90249, "wall_median_ms": 241.50215099962224}, "paired_ratio_median": 1.000629542926133, "paired_ratio_bootstrap_95_percent": [0.9983003180595498, 1.0009995969657295]}, "samples": {"async-std": {"scan_ms": [133.02739, 133.06117, 133.60235, 133.16965, 133.40722, 133.34133, 133.79982, 133.50019, 133.34729, 132.9543, 132.97513, 133.86926, 134.05539, 133.24223], "wall_ms": [239.632582, 239.299313, 240.69326, 241.236812, 245.210151, 240.884955, 240.974041, 241.96585, 239.067496, 241.208075, 254.893531, 245.278615, 248.441437, 240.973139]}, "tokio": {"scan_ms": [133.238, 133.49098, 133.4732, 132.9028, 133.5228, 133.4069, 133.90249, 133.85785, 133.1612, 133.06777, 133.46495, 133.47353, 133.05344, 133.39317], "wall_ms": [252.542821, 243.438741, 246.292204, 241.467916, 246.338636, 241.292069, 242.210823, 241.536386, 240.60291, 240.217646, 241.608773, 243.789372, 240.538868, 241.191347]}}},
{"case": "udp_silent_128_t5_b1", "arguments": ["--addresses", "127.0.0.1", "--range", "31000-31127", "--batch-size", "1", "--timeout", "5", "--tries", "1", "--udp"], "expected_open_count": 0, "expected_closed_count": 0, "summary": {"case": "udp_silent_128_t5_b1", "async-std": {"n": 12, "scan_median_ms": 647.45235, "scan_min_ms": 647.13037, "scan_max_ms": 647.9096, "wall_median_ms": 754.5120764998501}, "tokio": {"n": 12, "scan_median_ms": 647.5434499999999, "scan_min_ms": 646.6989, "scan_max_ms": 649.0831999999999, "wall_median_ms": 756.2571695002589}, "paired_ratio_median": 1.0001673335253631, "paired_ratio_bootstrap_95_percent": [0.9995790714638624, 1.0005779645783957]}, "samples": {"async-std": {"scan_ms": [647.3635, 647.20917, 647.3771, 647.33446, 647.413, 647.9096, 647.4971, 647.56674, 647.6953, 647.18795, 647.13037, 647.24445, 647.6764, 647.4917], "wall_ms": [758.592425, 755.975749, 758.079069, 754.578821, 754.257856, 754.087964, 754.511489, 754.512664, 754.525435, 757.162554, 757.284223, 753.779497, 753.794215, 754.096046]}, "tokio": {"scan_ms": [647.42637, 647.2082, 647.5038, 649.0832, 646.6989, 647.32325, 647.8038, 647.9199, 647.7853, 647.5831, 647.7122, 647.13156, 647.30984, 647.31306], "wall_ms": [756.138619, 753.755517, 757.528466, 753.601939, 757.316129, 757.89494, 753.950169, 756.446763, 754.649347, 756.228975, 753.679032, 754.926627, 756.285364, 758.323076]}}},
{"case": "tcp_open_16_interval1", "arguments": ["--addresses", "127.0.0.1", "--range", "20000-20015", "--batch-size", "256", "--timeout", "1500", "--tries", "1", "--interval", "1"], "expected_open_count": 16, "expected_closed_count": 0, "summary": {"case": "tcp_open_16_interval1", "async-std": {"n": 12, "scan_median_ms": 16.380881000000002, "scan_min_ms": 16.150762999999998, "scan_max_ms": 16.9349, "wall_median_ms": 127.33355999989726}, "tokio": {"n": 12, "scan_median_ms": 15.968566, "scan_min_ms": 15.782170000000002, "scan_max_ms": 16.192745000000002, "wall_median_ms": 123.29525399991326}, "paired_ratio_median": 0.9745702232857189, "paired_ratio_bootstrap_95_percent": [0.9638471208044207, 0.9843992590941175]}, "samples": {"async-std": {"scan_ms": [16.742926, 16.30988, 16.436962, 16.418753, 16.9349, 16.31153, 16.568368, 16.311154, 16.377501, 16.749995, 16.150763, 16.382523, 16.379239, 16.34811], "wall_ms": [123.728541, 124.449409, 127.732305, 129.316049, 129.626865, 127.054303, 128.552302, 126.115599, 127.535378, 127.131742, 127.636723, 123.361358, 125.967128, 126.564565]}, "tokio": {"scan_ms": [15.898872, 16.012024, 16.004669, 16.192745, 15.848257, 15.910927, 15.932463, 16.0934, 15.86936, 16.149458, 15.913244, 16.077776, 15.78217, 16.078446], "wall_ms": [123.129233, 124.075349, 123.24542, 125.239692, 121.153835, 122.962107, 123.345088, 123.750072, 123.947659, 123.936385, 123.59239, 122.694261, 122.874058, 122.894589]}}},
{"case": "tcp_open_16_interval20", "arguments": ["--addresses", "127.0.0.1", "--range", "20000-20015", "--batch-size", "256", "--timeout", "1500", "--tries", "1", "--interval", "20"], "expected_open_count": 16, "expected_closed_count": 0, "summary": {"case": "tcp_open_16_interval20", "async-std": {"n": 12, "scan_median_ms": 302.22183499999994, "scan_min_ms": 301.97242, "scan_max_ms": 302.5761, "wall_median_ms": 412.45387100025255}, "tokio": {"n": 12, "scan_median_ms": 301.93652499999996, "scan_min_ms": 301.76678000000004, "scan_max_ms": 302.2357, "wall_median_ms": 411.5444945005038}, "paired_ratio_median": 0.9989881724371461, "paired_ratio_bootstrap_95_percent": [0.9987076082418354, 0.9995525033875622]}, "samples": {"async-std": {"scan_ms": [302.18267, 302.13732, 301.9773, 302.5761, 302.25044, 301.97242, 302.143, 302.19373, 302.2255, 302.00857, 302.36325, 302.21817, 302.42342, 302.41114], "wall_ms": [413.653618, 410.964384, 412.493781, 411.729865, 412.494401, 411.493839, 418.091404, 412.413961, 414.137723, 415.698233, 410.759472, 412.03479, 412.157087, 413.086966]}, "tokio": {"scan_ms": [301.75188, 301.73197, 302.215, 301.85777, 301.83905, 301.76678, 302.06013, 301.8798, 301.8705, 302.0028, 301.99325, 301.77155, 302.2357, 302.11332], "wall_ms": [411.204909, 409.04968, 409.769905, 407.712868, 413.807731, 413.013744, 409.228791, 410.831539, 412.670222, 410.527218, 413.87037, 411.52089, 411.798223, 411.568099]}}}
],
"filtered_tcp_followup": {"purpose": "More samples for the noisy filtered TCP 20 ms result; separate from the 19-case run.", "trials": 40, "warmups": 2, "summary": {"case": "tcp_filtered_128_t20_retry2_b64", "async-std": {"n": 40, "scan_median_ms": 90.806345, "scan_min_ms": 83.67984, "scan_max_ms": 98.48335399999999, "wall_median_ms": 199.7979699995085}, "tokio": {"n": 40, "scan_median_ms": 89.66677, "scan_min_ms": 83.70783, "scan_max_ms": 98.51789, "wall_median_ms": 199.89575449972108}, "paired_ratio_median": 0.9904734730864315, "paired_ratio_bootstrap_95_percent": [0.9584064276305695, 1.0173184206016161]}, "samples": {"async-std": {"scan_ms": [88.323325, 92.4213, 88.936195, 92.37154, 96.92283, 84.850445, 89.15503, 92.10235, 83.80518, 92.24769, 92.32318, 84.8584, 92.456356, 88.23032, 92.04123, 92.18477, 86.79756, 98.483354, 89.1963, 91.340594, 83.67984, 93.583435, 92.54462, 89.31536, 91.27254, 88.60093, 84.68702, 93.62957, 92.30719, 88.04115, 84.974065, 94.37316, 89.16571, 95.54624, 93.07317, 84.02605, 90.34015, 96.3984, 90.19143, 90.06581, 96.61417, 85.11115], "wall_ms": [195.0308, 198.595023, 205.550084, 201.45625, 204.755249, 190.154735, 195.168845, 200.063899, 193.003685, 197.985884, 210.720138, 198.077596, 210.647758, 194.687149, 199.664278, 204.498946, 195.373439, 207.872652, 196.887853, 198.249704, 190.054223, 199.931662, 199.328127, 197.193407, 199.537635, 195.275976, 192.193237, 206.635061, 200.028809, 198.330338, 196.621195, 215.452312, 198.812027, 208.724402, 207.834586, 202.403001, 203.333875, 206.879636, 203.607022, 208.38006, 204.950703, 197.268808]}, "tokio": {"scan_ms": [92.301026, 93.38117, 93.45285, 94.5628, 88.58762, 84.90488, 89.12393, 91.01911, 96.5046, 88.37657, 98.51789, 88.854186, 84.06957, 91.783285, 87.75008, 94.83653, 90.99176, 89.60787, 90.4417, 92.33754, 92.54058, 89.72567, 83.76211, 93.127385, 87.82867, 83.70783, 84.0695, 87.62123, 90.449825, 89.82943, 92.07562, 88.124685, 90.16974, 83.91896, 87.90877, 89.39017, 84.15518, 93.53352, 86.91823, 85.3855, 89.75218, 98.28125], "wall_ms": [198.423459, 203.341894, 201.703401, 202.09388, 195.800999, 193.598472, 195.551461, 200.580659, 206.981415, 194.671571, 206.959222, 195.364517, 193.512965, 206.094437, 199.177046, 200.599854, 201.202883, 199.020153, 201.921695, 199.143086, 201.97124, 201.214532, 192.511818, 205.472348, 197.018051, 190.893376, 206.579472, 194.892288, 199.580795, 198.181881, 206.1589, 195.554125, 199.940732, 203.003288, 197.682916, 201.536251, 205.527831, 212.436077, 194.466474, 196.772168, 199.850777, 205.605438]}}},
"synthetic": {"operations": 4096, "batch_size": 256, "timeout_ms": 10, "samples_per_run": 20, "warmup_seconds": 1, "measurement_seconds": 5, "runtime_construction": "excluded", "ready_affinity": "CPU 0", "ready_order": ["async-std-1", "tokio-1", "tokio-2", "async-std-2"], "expired_affinity": "CPUs 0-3", "binary_sha256": {"async-std": "60e3fc23233d914f36a3d1913202a099440e926febcc36d37d6fcd3b79e7b867", "tokio": "64adfd7f5194bfc9ce7ad7c7875560f43727299b1b714c013a340a7353d57214"}, "ready": {"async-std": {"median_ms": 0.29120075443342, "runs": [{"median_ms": 0.283435356372549, "sample_ms": [0.2825720470588235, 0.28204494705882355, 0.28254624705882353, 0.28159535294117644, 0.2827166776470589, 0.28334777843137254, 0.2822799512605042, 0.28262099705882354, 0.28297270196078433, 0.28314464705882353, 0.2836823540106952, 0.2835229343137255, 0.28407015746606334, 0.2842827201680672, 0.2849620729411765, 0.2847762551470588, 0.285210384083045, 0.2895305209150327, 0.2874756, 0.2877200064705882]}, {"median_ms": 0.29475756493621547, "sample_ms": [0.2928709879518072, 0.2937114819277108, 0.2933189156626506, 0.29493332831325303, 0.2935973734939759, 0.2949348473895582, 0.29569418416523235, 0.295620109939759, 0.29519459571619816, 0.29707792530120486, 0.29418250054764516, 0.294691171686747, 0.2942722882298424, 0.29563811101549053, 0.29616976465863454, 0.2951352620481928, 0.2948239581856839, 0.2945176673360107, 0.29444605960684844, 0.2944720572289157]}]}, "tokio": {"median_ms": 0.302037698125, "runs": [{"median_ms": 0.30038876319444446, "sample_ms": [0.298729525, 0.29950843125000004, 0.3017063583333333, 0.30070625, 0.299885025, 0.3007033645833333, 0.3001008196428571, 0.2997647140625, 0.30044873055555554, 0.3001521925, 0.30016312272727275, 0.30431824270833335, 0.30060469615384616, 0.3011879410714286, 0.3012789341666666, 0.30614084375, 0.30109486838235294, 0.30032879583333333, 0.29896177302631577, 0.299311944375]}, {"median_ms": 0.3030938016369047, "sample_ms": [0.3012269375, 0.30389388125, 0.30436450416666666, 0.30480048125000003, 0.3020644975, 0.3028290625, 0.30420522678571427, 0.302707453125, 0.30225569861111107, 0.30201089875, 0.30262227954545456, 0.30319728541666663, 0.30556625480769234, 0.3029903178571428, 0.30277277, 0.30272753671875, 0.3081278419117647, 0.30388027500000003, 0.3033398302631579, 0.303353878125]}]}}, "expired": {"async-std": {"median_ms": 160.8818555, "sample_ms": [160.938174, 160.915957, 160.831742, 160.9983525, 160.883492, 160.906152, 160.858202, 160.7991255, 160.880219, 160.786231, 160.763265, 160.9403975, 160.8187525, 160.847539, 160.7793675, 160.8122135, 160.891816, 160.9187445, 160.945858, 161.08323]}, "tokio": {"median_ms": 160.96872275, "sample_ms": [161.0623005, 161.067578, 160.9593655, 160.9637835, 161.007304, 160.938726, 160.86018, 160.973662, 160.9531165, 160.9530565, 161.099919, 160.872232, 160.845637, 161.0776695, 160.8679585, 161.043725, 160.911652, 161.115825, 161.00697, 161.0155235]}}}
}
+229
View File
@@ -0,0 +1,229 @@
# Tokio scan benchmark
The runtime migration was compared with its async-std base using real TCP and UDP
sockets on 2026-10-01. This was a one-off, explicitly requested measurement in
an isolated Linux network namespace. No network benchmark was added to the
automated test or Criterion suites.
## Builds and environment
- Baseline: `cfb864590161d1c8a003811dcf4846ceb8c8284f`, async-std 1.13.2.
- Migration: `42427e88c1a6587d0a8b198a73bac4553bb4ad72`, Tokio 1.53.1.
- Both are optimized release binaries with the repository's LTO settings,
built using Rust 1.98.1.
- Intel Core Ultra 7 165U, Linux 7.2.6-1-cachyos, x86_64.
- Scanner processes were restricted to CPUs 0-3 (two physical performance
cores); the responder process used CPUs 4-7. Both runtimes used the same
affinity. The harness used CPUs 8-9.
- File descriptor soft limit: 65,536, so the requested batches of 256 and 4,500
were not reduced by RustScan's limit checks.
- The private namespace contained only loopback, with no external interface.
Targets used literal IPv4/IPv6 addresses; scripts and configuration loading
were disabled. The normal synchronous resolver initialization still occurred.
Binary SHA-256 hashes:
```text
async-std 00c7f0d91bab260ac622540e86083175afacfd1677647d2b8e96e56ba4f60732
tokio 669627834d8a32f60e2167e78a9518dcede7562831adb85fb61a9d94871494b8
```
## Method
Each workload used two warmup runs and twelve measured runs per binary. The
order alternated between async-std/Tokio and Tokio/async-std for each pair.
Each run started a fresh CLI process, matching ordinary command-line use.
The primary measurement is RustScan's `Portscan` timer, read using
`RUST_LOG=info`. It includes scan runtime initialization and cleanup and excludes
address parsing and final output. The wall time measures the entire process,
including startup, address parsing and output capture. Runtime startup was
excluded from the synthetic Criterion measurements below, so those numbers
measure a different workload.
The responder bound 512 TCP listeners, 256 UDP echo sockets and 128 silent UDP
sockets on each of `127.0.0.1` and `::1`. Empty UDP requests received empty replies.
TCP connections were accepted and closed immediately. Silent UDP sockets received
datagrams without replying. A firewall rule inside the private namespace dropped
TCP packets to `127.0.0.3:40000-40127`, producing actual connection timeouts.
No listeners were bound to `127.0.0.2`, used for the all-closed TCP scans.
Every run checked the exact set of reported open ports and rejected duplicates.
A separate check verified closed-port reporting and port exclusions for both
binaries. Each invocation used this common prefix:
```sh
RUST_LOG=info taskset -c 0-3 /path/to/rustscan \
--no-config --config-path /dev/null --no-banner --scripts none \
--greppable --scan-order serial
```
Append the arguments for the workload:
| Workload | Arguments |
| --- | --- |
| TCP closed, 65,535 ports | `-a 127.0.0.2 -r 1-65535 -b 256` or `-b 4500` |
| TCP mixed, 512 open / 4,096 ports | `-a 127.0.0.1 -r 20000-24095 -b 256` or `-b 4500` |
| TCP open, 512 ports | `-a 127.0.0.1 -r 20000-20511 -b 4500` |
| TCP IPv6 mixed, 512 open / 4,096 ports | `-a ::1 -r 20000-24095 -b 4500` |
| UDP echo, 256 ports | `-a 127.0.0.1 -r 30000-30255 -b 256 --udp` |
| UDP IPv6 echo, 256 ports | `-a ::1 -r 30000-30255 -b 256 --udp` |
| TCP filtered, short timeout and retries | `-a 127.0.0.3 -r 40000-40127 -b 64 -t 20 --tries 2` |
| TCP filtered, default timeout | `-a 127.0.0.3 -r 40000-40127 -b 4500 -t 1500` |
| UDP silent, short timeout and retries | `-a 127.0.0.1 -r 31000-31127 -b 64 -t 20 --tries 2 --udp` |
| UDP silent, default timeout | `-a 127.0.0.1 -r 31000-31127 -b 4500 -t 1500 --udp` |
| TCP open, 16 ports with intervals | `-a 127.0.0.1 -r 20000-20015 -b 256 --interval 1` (also 5 and 20 ms) |
| TCP filtered, short serial timeouts | `-a 127.0.0.3 -r 40000-40127 -b 1 -t 1` (also 5 ms) |
| UDP silent, short serial timeouts | `-a 127.0.0.1 -r 31000-31127 -b 1 -t 1 --udp` (also 5 ms) |
All unspecified timeouts use 1,500 ms and one try. The mixed TCP workloads have
listeners on ports 20000-20511 and no listeners on the remaining ports.
## Results
These results cover the revised timeout implementation. The initial migration
added 20.1% to the 5 ms interval workload (76.45 to 91.79 ms). Port intervals and
short deadlines now retain the existing `async_io::Timer`; long deadlines use
Tokio until 32 ms before expiry, then finish with the precise timer. Both phases
preserve the original deadline. This avoids timer rounding on short waits and
avoids registering a second timer for normal early socket replies.
All values below are medians of twelve measured runs. Negative changes mean
less elapsed time. The final column is a bootstrap 95% interval for the median
of paired Tokio/async-std scan-time ratios, expressed as a percentage change.
It uses 5,000 resamples of twelve pairs and does not account for every source
of system noise. It differs from the ratio of the two medians in the Change
column. [Per-run timings and summaries](tokio-benchmark-results.json) include
the warmups and full precision.
| Workload | async-std scan (ms) | Tokio scan (ms) | Change | Paired change, 95% interval |
| --- | ---: | ---: | ---: | ---: |
| TCP closed, 65,535 ports, batch 256 | 565.62 | 377.77 | -33.21% | -34.85% to -31.97% |
| TCP closed, 65,535 ports, batch 4,500 | 769.26 | 571.34 | -25.73% | -27.30% to -23.46% |
| TCP mixed, 4,096 ports, batch 256 | 68.14 | 27.08 | -60.26% | -62.18% to -56.78% |
| TCP mixed, 4,096 ports, batch 4,500 | 105.18 | 40.72 | -61.28% | -62.90% to -58.33% |
| TCP open, 512 ports, batch 4,500 | 47.70 | 6.26 | -86.87% | -88.76% to -85.17% |
| TCP IPv6 mixed, 4,096 ports, batch 4,500 | 106.26 | 40.98 | -61.43% | -63.84% to -60.18% |
| UDP echo, 256 ports, batch 256 | 26.10 | 3.55 | -86.40% | -89.28% to -83.06% |
| UDP IPv6 echo, 256 ports, batch 256 | 27.63 | 3.80 | -86.25% | -88.36% to -84.90% |
| TCP filtered, 20 ms, two tries, batch 64 | 87.64 | 90.20 | +2.92% | -0.36% to +6.30% |
| TCP filtered, 1,500 ms, batch 4,500 | 1519.99 | 1500.68 | -1.27% | -1.40% to -1.07% |
| UDP silent, 20 ms, two tries, batch 64 | 91.01 | 82.37 | -9.49% | -13.15% to -6.01% |
| UDP silent, 1,500 ms, batch 4,500 | 1518.53 | 1501.66 | -1.11% | -1.35% to -1.01% |
| TCP open, 16 ports, 5 ms interval | 76.53 | 76.20 | -0.43% | -0.59% to -0.28% |
| TCP filtered, 1 ms, batch 1 | 130.93 | 131.10 | +0.13% | -0.00% to +0.22% |
| TCP filtered, 5 ms, batch 1 | 644.51 | 644.95 | +0.07% | -0.03% to +0.10% |
| UDP silent, 1 ms, batch 1 | 133.38 | 133.44 | +0.04% | -0.17% to +0.10% |
| UDP silent, 5 ms, batch 1 | 647.45 | 647.54 | +0.01% | -0.04% to +0.06% |
| TCP open, 16 ports, 1 ms interval | 16.38 | 15.97 | -2.52% | -3.62% to -1.56% |
| TCP open, 16 ports, 20 ms interval | 302.22 | 301.94 | -0.09% | -0.13% to -0.04% |
Bulk TCP scans took 25.7-86.9% less scan time, and UDP echo scans took
86.2-86.4% less. All three interval workloads improved; the former 5 ms
regression is gone. Serial 1 ms and 5 ms timeout medians took 0.01-0.13% more
time. Default 1,500 ms timeout scans took about 1.1-1.3% less time.
These measurements support substantial bulk-scan gains, but **do not establish
literally zero regressions**. The noisy filtered TCP case and the remaining
synthetic overhead are detailed below.
### Follow-up on the noisy filtered TCP case
The 20 ms filtered TCP workload had a 2.92% higher median in the main run, with
substantial variation. A separate repeat used the same binaries, setup and
alternating order, with two warmups and **40 measured runs per binary**:
| Measurement | async-std | Tokio | Change |
| --- | ---: | ---: | ---: |
| Scan median | 90.81 ms | 89.67 ms | -1.25% |
| Entire CLI median | 199.80 ms | 199.90 ms | +0.05% |
The paired scan-ratio bootstrap 95% interval was **-4.16% to +1.73%**. The
repeat does not establish a slowdown, but it cannot rule out a small change
in this noisy case. Its 84 runs all returned the expected results. The original
result remains in the table above; the JSON includes both runs.
### Entire CLI process
Address parsing and startup contribute substantial time on this machine, whose
hosts file is approximately 2.7 MB. Total process changes are consequently
smaller than scan-phase changes. These figures include startup, address parsing,
output capture and process exit:
| Workload | async-std wall (ms) | Tokio wall (ms) | Change |
| --- | ---: | ---: | ---: |
| TCP closed, 65,535 ports, batch 256 | 678.41 | 490.92 | -27.64% |
| TCP closed, 65,535 ports, batch 4,500 | 886.49 | 686.16 | -22.60% |
| TCP mixed, 4,096 ports, batch 256 | 187.83 | 141.97 | -24.42% |
| TCP mixed, 4,096 ports, batch 4,500 | 223.57 | 156.73 | -29.90% |
| TCP open, 512 ports, batch 4,500 | 160.65 | 124.34 | -22.61% |
| TCP IPv6 mixed, 4,096 ports, batch 4,500 | 225.67 | 154.43 | -31.57% |
| UDP echo, 256 ports, batch 256 | 144.53 | 117.11 | -18.97% |
| UDP IPv6 echo, 256 ports, batch 256 | 145.42 | 119.61 | -17.75% |
| TCP filtered, 20 ms, two tries, batch 64 | 203.20 | 203.25 | +0.02% |
| TCP filtered, 1,500 ms, batch 4,500 | 1625.54 | 1607.90 | -1.08% |
| UDP silent, 20 ms, two tries, batch 64 | 198.82 | 193.47 | -2.69% |
| UDP silent, 1,500 ms, batch 4,500 | 1626.34 | 1608.17 | -1.12% |
| TCP open, 16 ports, 5 ms interval | 184.30 | 182.87 | -0.78% |
| TCP filtered, 1 ms, batch 1 | 239.22 | 239.95 | +0.31% |
| TCP filtered, 5 ms, batch 1 | 753.12 | 753.69 | +0.08% |
| UDP silent, 1 ms, batch 1 | 241.22 | 241.50 | +0.12% |
| UDP silent, 5 ms, batch 1 | 754.51 | 756.26 | +0.23% |
| TCP open, 16 ports, 1 ms interval | 127.33 | 123.30 | -3.17% |
| TCP open, 16 ports, 20 ms interval | 412.45 | 411.54 | -0.22% |
### Correctness
All 532 runs (456 measured and 76 warmups) returned exactly the expected open
ports, with no duplicates or missing replies. Both binaries also passed the
separate closed-port and exclusion check. Responder counters matched the expected
58,702 accepted TCP connections, 14,336 echoed UDP datagrams and 17,920 silent UDP
datagrams, including retries. The isolated firewall counted 21,504 dropped TCP
packets during the filtered-port workloads.
## Synthetic scheduling comparison
These use simulated operations without sockets, DNS, or scripts. Each iteration
completes 4,096 operations through a 256-operation window. One workload replies
immediately; the other expires every operation after 10 ms. Both builds use the
same loop; the baseline uses `async_std::io::timeout` and its original
`futures::executor::block_on`, while the migration uses the production timeout
helper and Tokio. Runtime construction is outside the measurement.
Each Criterion run has twenty samples, a one-second warmup and a five-second
measurement target. The ready case was repeated on CPU 0 in A/B/B/A order to
reduce core-migration and run-order effects. Its table value is the median of
the forty per-sample batch times for each binary. The expiration case used one
run per binary on CPUs 0-3. No builds or socket benchmarks ran concurrently.
| Simulated workload | async-std median | Tokio median | Change |
| --- | ---: | ---: | ---: |
| Immediately ready operations | 0.29120 ms | 0.30204 ms | +3.72% |
| Every operation expires after 10 ms | 160.88186 ms | 160.96872 ms | +0.05% |
The ready case still costs about **11 microseconds more per 4,096 operations**.
Its two individual run medians were 0.28344/0.29476 ms for async-std and
0.30039/0.30309 ms for Tokio. This remaining overhead is included rather than
claiming that every workload improved. The original 11% synthetic expiration
regression has been reduced to about 0.05% in this comparison.
Reproduce the migration benchmark with:
```sh
cargo bench --locked --bench benchmark_helpers -- 'runtime scheduling'
```
For the baseline, apply [the benchmark adapter](tokio-benchmark-baseline.patch)
with `git apply --unidiff-zero` to a checkout of
`cfb864590161d1c8a003811dcf4846ceb8c8284f`, then run the same benchmark command.
The JSON includes per-sample times and benchmark binary hashes.
## Scope
These results apply to this machine and its loopback stack. They do not establish
throughput across a LAN or WAN, under packet loss, or on macOS or Windows. The
single Python responder can affect reply timing, and the host was an ordinary
interactive system. Both binaries used the same responder and CPU restrictions,
and every expected reply was checked. A finite benchmark cannot guarantee zero
slowdown for every workload or distinguish every sub-percent difference from
system noise.
+104
View File
@@ -0,0 +1,104 @@
# Tokio runtime migration
This implements the runtime migration discussed in [#856](https://github.com/bee-san/RustScan/pull/856)
and [#944](https://github.com/bee-san/RustScan/issues/944), based on master at
`cfb864590161d1c8a003811dcf4846ceb8c8284f`.
## CLI behavior
TCP connections and UDP sockets now use Tokio. The CLI creates a runtime on the calling thread for the
scan. `FuturesUnordered` still polls at most `batch_size` socket attempts at once;
concurrent sockets do not require a thread per socket or a task per socket.
Address parsing and Hickory's synchronous resolver run before entering the scan
runtime. Script execution runs after the runtime has been dropped. Port ordering,
exclusions, retries, UDP payloads, closed-port reporting and CLI options retain
their existing implementations.
Port intervals and short timeouts retain `async_io::Timer`, the timer underneath
async-std, independently of the Tokio socket runtime. Longer timeouts first use
Tokio's timer until 32 ms before the deadline, then use `async_io::Timer` for the
remaining wait. This avoids registering a second timer for ordinary replies
while leaving room for coarse timer rounding. Both phases use the original
deadline, including time spent setting up the socket.
An expired timer maps to `std::io::ErrorKind::TimedOut`. Socket errors retain
their original kind, so TCP refusal reporting and UDP timeout retries continue
to distinguish replies from failures. Successful TCP streams are converted to
standard streams, shut down in both directions, and dropped before another
attempt is polled.
Using Tokio's timers for short waits added approximately one millisecond per
interval in the first benchmark. Retaining the existing timer avoids that extra
rounding while keeping the async runtime responsive.
Dropping a scan cancels its timers; runtime shutdown never has to wait for a
sleeping blocking task. Immediately ready I/O operations avoid timer allocation,
and the time spent polling a pending operation is charged to its original timeout.
## Library callers
`Scanner::new`, `run`, `run_with_status`, `PortStrategy`, and the address helpers
keep their signatures. However, awaiting a scan now requires a Tokio runtime
with both I/O and time enabled. Calling it from `futures::executor::block_on` or
`async_std::task::block_on` alone is no longer sufficient.
Treat this new runtime requirement as a **breaking library change for the next
major release**. This PR does not publish a release or change the package version.
Add Tokio to the embedding application's dependencies:
```toml
tokio = { version = "1.53.1", features = ["rt", "net", "time"] }
```
For a synchronous application, resolve inputs first, then create the runtime:
```rust,ignore
let ips = rustscan::address::parse_addresses(&opts);
let scanner = Scanner::new(/* existing arguments, including &ips */);
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
let sockets = runtime.block_on(scanner.run());
```
The [crate example](../src/lib.rs) provides a complete, compile-checked example.
Applications already running on Tokio can simply use `scanner.run().await` or
`scanner.run_with_status().await`; do not nest `Runtime::block_on` calls.
Keep the synchronous address helpers outside async runtime execution. If an
existing Tokio application needs to call `parse_addresses`, move owned options
into `tokio::task::spawn_blocking(move || parse_addresses(&opts)).await` and handle
the join result before constructing the scanner. Hickory's synchronous resolver
owns its own runtime and performs blocking lookups.
## Dependencies and validation
The direct Tokio dependency enables only `rt`, `net`, and `time`; `test-util` is
enabled for deterministic tests with a paused clock. Tokio was already present
through Hickory. The existing `futures` dependency continues to provide
`FuturesUnordered`, `StreamExt` and future polling. `async-io` becomes a direct
dependency solely to retain the existing timer implementation and its short-wait
performance; socket I/O uses Tokio. `async-std` and its executor are absent from
the resolved dependency tree.
Regression tests cover successful operations (including zero-byte responses),
preservation of I/O errors, timeout cancellation, immediate results at a zero
deadline, wakeups during the first poll, scan cancellation, and interval timing with
excluded or empty port sets. They use simulated operations and scanners with no
target addresses, following the repository's rule against network tests.
Run the repository checks with `just test`. Run the isolated scheduling
microbenchmarks with:
```sh
cargo bench --locked --bench benchmark_helpers -- 'runtime scheduling'
```
Each benchmark completes 4,096 simulated operations through a 256-operation
window. One case completes immediately; the other expires every operation after
10 ms. Runtime construction is outside the measurement. Criterion uses 20
samples, a one-second warmup and a five-second measurement target. These measure
scheduling and timer costs. The [real TCP/UDP scan comparison](tokio-benchmark.md)
records a separate measurement in an isolated loopback environment, including
full CLI timings and short port intervals. LAN/WAN performance
and network performance on other operating systems remain unmeasured.
+9 -2
View File
@@ -7,10 +7,13 @@
//! [`Scanner`](crate::scanner::Scanner) which in turn requires a
//! [`PortStrategy`](crate::port_strategy::PortStrategy):
//!
//! Scans require a Tokio runtime with I/O and time enabled. Synchronous address
//! parsing should run before entering the runtime; see the
//! [runtime migration guide](https://github.com/bee-san/RustScan/blob/master/docs/tokio-migration.md).
//!
//! This example is compiled but never executed by the test suite.
//!
//! ```no_run
//! use async_std::task::block_on;
//! use std::{net::IpAddr, time::Duration};
//!
//! use rustscan::input::{PortRanges, ScanOrder};
@@ -33,7 +36,11 @@
//! false, // is this a UDP scan?
//! );
//!
//! let scan_result = block_on(scanner.run());
//! let runtime = tokio::runtime::Builder::new_current_thread()
//! .enable_all()
//! .build()
//! .unwrap();
//! let scan_result = runtime.block_on(scanner.run());
//!
//! println!("{:?}", scan_result);
//! }
+8 -2
View File
@@ -11,7 +11,6 @@ use rustscan::tui::println_safe;
use rustscan::{detail, funny_opening, output, warning};
use colorful::{Color, Colorful};
use futures::executor::block_on;
use std::collections::HashMap;
use std::net::IpAddr;
use std::string::ToString;
@@ -108,7 +107,14 @@ fn main() {
debug!("Scanner finished building: {scanner:?}");
let mut portscan_bench = NamedTimer::start("Portscan");
let scan_result = block_on(scanner.run_with_status());
// Keep synchronous DNS resolution and script execution outside the runtime.
// FuturesUnordered drives the bounded scan concurrently on this thread.
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create the Tokio runtime");
let scan_result = runtime.block_on(scanner.run_with_status());
drop(runtime);
portscan_bench.end();
benchmarks.push(portscan_bench);
+217 -16
View File
@@ -5,22 +5,23 @@ use crate::tui::println_safe;
use log::debug;
mod socket_iterator;
mod timeout;
use socket_iterator::SocketIterator;
use timeout::io_timeout;
use async_std::net::TcpStream;
use async_std::prelude::*;
use async_std::task::sleep;
use async_std::{io, net::UdpSocket};
use colored::Colorize;
use futures::stream::FuturesUnordered;
use futures::{stream::FuturesUnordered, StreamExt};
use std::collections::BTreeMap;
use std::{
collections::{HashMap, HashSet},
future::Future,
io,
net::{IpAddr, Shutdown, SocketAddr},
num::NonZeroU8,
sync::Arc,
time::Duration,
};
use tokio::net::{TcpStream, UdpSocket};
/// UDP payload lookup: port -> payload bytes
///
@@ -142,6 +143,8 @@ impl Scanner {
/// Runs scan_range with chunk sizes
/// If you want to run RustScan normally, this is the entry point used
/// Returns all open sockets.
///
/// Must be awaited inside a Tokio runtime with I/O and time enabled.
pub async fn run(&self) -> Vec<SocketAddr> {
self.run_with_status()
.await
@@ -156,7 +159,24 @@ impl Scanner {
/// Like [`Self::run`], but returns the status of every socket that gave a
/// definitive answer: open sockets, plus closed sockets when
/// [`Self::with_closed_ports`] is enabled.
///
/// Must be awaited inside a Tokio runtime with I/O and time enabled.
pub async fn run_with_status(&self) -> Vec<PortStatus> {
// Tokio's millisecond rounding adds about 1 ms to each short interval.
// This cancellable delay keeps the requested spacing without blocking
// the async runtime or accumulating that rounding on every port.
self.run_with_delay(|interval| async move {
async_io::Timer::after(interval).await;
})
.await
}
/// Supplying the delay lets tests verify spacing with a virtual clock.
async fn run_with_delay<D, F>(&self, delay: D) -> Vec<PortStatus>
where
D: Fn(Duration) -> F,
F: Future<Output = ()>,
{
let ports: Vec<u16> = self
.port_strategy
.order()
@@ -192,7 +212,7 @@ impl Scanner {
// before moving on to the next port.
for (i, port) in ports.iter().enumerate() {
if i > 0 {
sleep(self.interval).await;
delay(self.interval).await;
}
let sockets = SocketIterator::new(&self.ips, std::slice::from_ref(port));
self.scan_sockets(sockets, &udp_payloads, &mut found_sockets, &mut errors)
@@ -269,7 +289,10 @@ impl Scanner {
match self.connect(socket).await {
Ok(tcp_stream) => {
debug!("Connection was successful, shutting down stream {}", socket);
if let Err(e) = tcp_stream.shutdown(Shutdown::Both) {
if let Err(e) = tcp_stream
.into_std()
.and_then(|stream| stream.shutdown(Shutdown::Both))
{
debug!("Shutdown stream error {}", e);
}
self.fmt_ports(socket);
@@ -339,12 +362,7 @@ impl Scanner {
/// ```
///
async fn connect(&self, socket: SocketAddr) -> io::Result<TcpStream> {
let stream = io::timeout(
self.timeout,
async move { TcpStream::connect(socket).await },
)
.await?;
Ok(stream)
io_timeout(self.timeout, TcpStream::connect(socket)).await
}
/// Binds to a UDP socket so we can send and receive packets
@@ -398,7 +416,7 @@ impl Scanner {
udp_socket.connect(socket).await?;
udp_socket.send(payload).await?;
match io::timeout(wait, udp_socket.recv(&mut buf)).await {
match io_timeout(wait, udp_socket.recv(&mut buf)).await {
Ok(size) => {
debug!("Received {size} bytes");
self.fmt_ports(socket);
@@ -449,8 +467,191 @@ mod tests {
use crate::input::{PortRanges, ScanOrder};
// These tests never open sockets: they only build a `Scanner` or inspect
// the payload table generated by build.rs.
// These tests never open sockets: operations are simulated futures, and
// scanners that run have no target addresses.
fn test_runtime() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.start_paused(true)
.build()
.unwrap()
}
#[test]
fn timeout_preserves_success_including_empty_udp_responses() {
test_runtime().block_on(async {
// A zero-byte UDP datagram is still a successful response.
let result = io_timeout(Duration::from_secs(1), async { Ok(0_usize) }).await;
assert_eq!(result.unwrap(), 0);
});
}
#[test]
fn timeout_preserves_io_errors() {
test_runtime().block_on(async {
for kind in [
io::ErrorKind::ConnectionRefused,
io::ErrorKind::PermissionDenied,
io::ErrorKind::TimedOut,
] {
let result = io_timeout(Duration::from_secs(1), async {
Err::<(), _>(io::Error::new(kind, "socket error"))
})
.await;
let error = result.unwrap_err();
assert_eq!(error.kind(), kind);
assert_eq!(error.to_string(), "socket error");
}
});
}
#[test]
fn ready_operation_wins_at_zero_timeout() {
test_runtime().block_on(async {
assert_eq!(
io_timeout(Duration::ZERO, async { Ok(7) }).await.unwrap(),
7
);
});
}
#[test]
fn timeout_preserves_wakes_from_the_first_poll() {
test_runtime().block_on(async {
let mut polled = false;
let operation = std::future::poll_fn(|context| {
if polled {
std::task::Poll::Ready(Ok(7))
} else {
polled = true;
context.waker().wake_by_ref();
std::task::Poll::Pending
}
});
assert_eq!(
io_timeout(Duration::from_secs(1), operation).await.unwrap(),
7
);
});
}
#[test]
fn expired_timeout_cancels_the_operation() {
use std::cell::Cell;
struct DropGuard<'a>(&'a Cell<bool>);
impl Drop for DropGuard<'_> {
fn drop(&mut self) {
self.0.set(true);
}
}
for duration in [Duration::from_millis(5), Duration::from_millis(80)] {
test_runtime().block_on(async {
let dropped = Cell::new(false);
let started = std::time::Instant::now();
let operation = async {
let _guard = DropGuard(&dropped);
std::future::pending::<io::Result<()>>().await
};
let error = io_timeout(duration, operation).await.unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::TimedOut);
assert!(started.elapsed() >= duration);
assert!(dropped.get(), "the timed-out operation must be dropped");
});
}
}
#[test]
fn operation_can_complete_before_the_precise_timeout_phase() {
test_runtime().block_on(async {
let result = io_timeout(Duration::from_secs(1), async {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(7)
})
.await;
assert_eq!(result.unwrap(), 7);
});
}
#[test]
fn interval_waits_only_between_included_ports() {
test_runtime().block_on(async {
let interval = Duration::from_millis(250);
let mut scanner = test_scanner().with_interval(interval);
scanner.ips.clear();
scanner.port_strategy = PortStrategy::Manual(vec![80, 443, 8080, 8443]);
scanner.exclude_ports = vec![443];
let started = tokio::time::Instant::now();
assert!(scanner.run_with_delay(tokio::time::sleep).await.is_empty());
assert_eq!(started.elapsed(), interval * 2);
});
}
#[test]
fn dropping_a_scan_cancels_a_pending_interval() {
use std::cell::Cell;
use std::task::Poll;
struct PendingDelay<'a>(&'a Cell<bool>);
impl Future for PendingDelay<'_> {
type Output = ();
fn poll(self: std::pin::Pin<&mut Self>, _: &mut std::task::Context<'_>) -> Poll<()> {
Poll::Pending
}
}
impl Drop for PendingDelay<'_> {
fn drop(&mut self) {
self.0.set(true);
}
}
test_runtime().block_on(async {
let dropped = Cell::new(false);
let mut scanner = test_scanner().with_interval(Duration::from_secs(3600));
scanner.ips.clear();
scanner.port_strategy = PortStrategy::Manual(vec![80, 443]);
let mut scan = Box::pin(scanner.run_with_delay(|_| PendingDelay(&dropped)));
assert!(futures::poll!(scan.as_mut()).is_pending());
assert!(!dropped.get());
drop(scan);
assert!(dropped.get());
});
}
#[test]
fn zero_interval_and_empty_port_sets_do_not_wait() {
test_runtime().block_on(async {
let mut scanner = test_scanner();
scanner.ips.clear();
scanner.port_strategy = PortStrategy::Manual(vec![80, 443]);
let started = tokio::time::Instant::now();
assert!(scanner.run().await.is_empty());
assert_eq!(started.elapsed(), Duration::ZERO);
scanner = scanner.with_interval(Duration::from_secs(1));
scanner.exclude_ports = vec![80, 443];
assert!(scanner.run().await.is_empty());
assert_eq!(started.elapsed(), Duration::ZERO);
scanner.port_strategy = PortStrategy::Manual(Vec::new());
assert!(scanner.run().await.is_empty());
assert_eq!(started.elapsed(), Duration::ZERO);
});
}
fn test_scanner() -> Scanner {
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
+57
View File
@@ -0,0 +1,57 @@
use std::{
future::{poll_fn, Future},
io,
task::Poll,
time::{Duration, Instant},
};
/// Preserve I/O errors and cancel pending operations when their deadline expires.
/// Poll once before allocating a timer so immediate replies pay no timer cost.
pub(super) async fn io_timeout<T>(
duration: Duration,
operation: impl Future<Output = io::Result<T>>,
) -> io::Result<T> {
let started = Instant::now();
futures::pin_mut!(operation);
let mut delay = None;
// Most replies arrive well before a normal scan timeout. Keep their timers
// on Tokio's reactor to avoid registering each socket's deadline with a
// second reactor. Leave 32 ms for the precise timer to allow for coarse
// timer rounding (including Windows). Short timeouts use the
// precise timer immediately; neither phase changes the original deadline.
// Allocate this state only for pending I/O. Keeping it out of the outer
// future also keeps FuturesUnordered entries small for immediate results.
poll_fn(|context| {
if let Poll::Ready(result) = operation.as_mut().poll(context) {
return Poll::Ready(result);
}
let delay = delay.get_or_insert_with(|| {
Box::pin(async move {
let Some(deadline) = started.checked_add(duration) else {
return std::future::pending::<()>().await;
};
const PRECISE_WINDOW: Duration = Duration::from_millis(32);
if deadline.saturating_duration_since(Instant::now()) > PRECISE_WINDOW {
tokio::time::sleep_until(tokio::time::Instant::from_std(
deadline - PRECISE_WINDOW,
))
.await;
}
// OS waits avoid rounding every short timeout to the next millisecond.
async_io::Timer::at(deadline).await;
})
});
if delay.as_mut().poll(context).is_ready() {
Poll::Ready(Err(io::Error::new(
io::ErrorKind::TimedOut,
"future timed out",
)))
} else {
Poll::Pending
}
})
.await
}