mirror of
https://github.com/bee-san/RustScan.git
synced 2026-11-07 09:46:04 -10:00
master
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
937903340a |
Avoid formatting discarded scan errors (#950)
* perf(scanner): defer TCP error diagnostics * bench(scanner): check open-only TCP and UDP paths * fix(scanner): Import HashSet in tests after main merge --------- Co-authored-by: Autumn Skerritt <[email protected]> |
||
|
|
67c08a3310 |
Speed up port exclusions and avoid redundant port copies (#954)
* test(scanner): preserve port filtering order and boundaries * perf(scanner): reuse port lists and index large exclusions * perf(scanner): use bitmap for dense exclusions * bench(scanner): document production port preparation speedups * ci(bench): diagnose repeated macOS listener misses * ci(bench): capture socket errors immediately after listener misses * perf(scanner): keep bitmap storage out of repeated async polls * ci(bench): provision macOS TCP memory for repeated scans |
||
|
|
b7119f7f3e |
Port to tokio (supersedes #856, closes #944) (#951)
* refactor(scanner): run the scanner on tokio instead of async-std
async-std is discontinued upstream (RUSTSEC-2025-0052). tokio is already in
the dependency tree through hickory-resolver, so switching adds no crate and
removes 28 from Cargo.lock (async-std, async-io, polling,
async-global-executor, blocking, futures-executor, ...).
This is a like-for-like port. The scanner keeps its architecture and the
library API keeps its signatures:
- a single `FuturesUnordered` keeps at most `batch_size` sockets in flight
from one future; nothing is spawned per socket;
- connect and UDP receive timeouts use `tokio::time::timeout` and report the
same `TimedOut` error that async-std's `io::timeout` did;
- open TCP sockets are still shut down in both directions before they are
closed (through `into_std()`, because tokio only has a write-side shutdown);
- `--interval` sleeps with `tokio::time::sleep`.
The binary runs the scan on a current-thread runtime that exists only for
the scan. Address resolution (blocking DNS lookups and file reads) runs
before the runtime is created and scripts run after it is dropped, so neither
can stall the scan, and hickory's internal runtime is never nested in ours.
Library users now have to poll the scan futures from a tokio runtime with the
I/O and time drivers enabled. The crate and `Scanner` docs say so, and the
crate example shows how.
Builds on the earlier port in #856 by @vrtgs.
Co-authored-by: Vrtgs <[email protected]>
* perf(scanner): opt the scan out of tokio's cooperative budget
The scan polls every in-flight socket from one future through a
`FuturesUnordered`. tokio's cooperative scheduling gives that future a budget
of 128 units per poll. Once it is spent, every tokio resource returns
`Pending` and defers its wake-up, but `FuturesUnordered` still polls all the
other ready sockets in that same poll. When thousands of connections finish
together (a burst of refusals, or a batch of timeouts firing at once), the
scan keeps re-polling sockets only to park them again, roughly 128 at a time.
async-std had no budget. `tokio::task::unconstrained` restores that
behaviour: one poll handles every ready socket. The future still returns
`Pending`, and so lets the runtime poll I/O and timers, as soon as no socket
is ready.
* perf(scanner): send UDP probes before registering the socket with tokio
tokio's socket I/O is readiness based: `UdpSocket::send` and `recv` first
wait until the reactor has reported the socket ready and only then make the
system call. async-io, which async-std used, made the call first and only
waited after `WouldBlock`.
With a straight port, every UDP probe therefore made an extra trip through
the event loop before its send. Probes to closed ports on the local host
also no longer finished in their first poll: there the ICMP "port
unreachable" is usually queued before `send` returns, so async-std's first
`recv` already saw the refusal.
Bind a non-blocking std socket instead, send the probe and try the first
receive straight away. The socket is only handed to tokio, and registered
with its reactor, when there is really something to wait for. Errors and
timeouts are reported exactly as before.
* ci: add an opt-in scan benchmark comparing a change with its base
The test suite deliberately never opens sockets (see #942), so nothing in CI
showed whether a change to the scanner makes RustScan slower, hungrier or
wrong. The tokio port (#944) needs exactly that evidence.
The "Scan benchmark" workflow runs for pull requests that touch the scanner,
its runtime or its dependencies, and on demand. It builds the change and its
base in release mode on Linux x64/arm64, macOS and Windows runners. It then
runs .github/scan-benchmark/scan_bench.py, which:
- opens 315 TCP listeners and 32 UDP responders on 127.0.0.1 inside the
runner. Nothing outside the runner is scanned. Ports stay out of the
ephemeral range, so a scan can never connect a socket to itself;
- scans them with both builds, alternating the build order between runs:
full TCP sweeps with batch sizes 500/4500/10000, 8 loopback hosts, one
port, a UDP sweep, and on Linux ports that iptables drops (filtered TCP and
UDP, and a mixed sweep), so timeouts are measured too;
- fails unless every run finds every listener and no build reports ports the
base never found, or misses ports the base always found;
- reports medians of throughput (from RustScan's own scan timer), wall time,
time to the first open port, peak RSS, CPU time, and sampled peak
descriptors and threads. The table goes to the job summary, the raw data
to an artifact;
- fails if the change's median throughput or wall time is more than 15%
worse, or its peak RSS more than 25% worse, in any scenario.
contributing.md documents the workflow as the place where scanning is
measured, next to the rule that tests must not scan.
* ci(scan-benchmark): temporarily compare the port's intermediate builds (to be reverted)
* Revert "ci(scan-benchmark): temporarily compare the port's intermediate builds (to be reverted)"
This reverts commit
|