security: Add attack binary

Add a binary that deploys a flooding attack.

Also, add a binary that periodically queries a random URL from the website. This
scraper will be used to measure whether the attack (or its countermeasure) is
effective.
This commit is contained in:
Alan Szepieniec 2025-10-09 18:32:51 +02:00
parent ec6d99e9d0
commit edf7800a88
4 changed files with 494 additions and 8 deletions

355
Cargo.lock generated
View File

@ -223,6 +223,12 @@ dependencies = [
"syn 2.0.104",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "attribute-derive"
version = "0.10.3"
@ -848,6 +854,15 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "enum-ordinalize"
version = "3.1.15"
@ -861,6 +876,29 @@ dependencies = [
"syn 2.0.104",
]
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@ -1098,6 +1136,25 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "h2"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap 2.10.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -1231,6 +1288,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
@ -1239,6 +1297,39 @@ dependencies = [
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
@ -1247,14 +1338,24 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
@ -1441,6 +1542,22 @@ dependencies = [
"libc",
]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@ -1471,6 +1588,30 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "js-sys"
version = "0.3.77"
@ -1576,9 +1717,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.27"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "manyhow"
@ -1799,21 +1940,25 @@ dependencies = [
"chrono",
"clap",
"derive_more",
"env_logger",
"html-escaper",
"indexmap 2.10.0",
"lettre",
"log",
"neptune-cash",
"proptest",
"proptest-arbitrary-interop",
"rand 0.9.2",
"readonly",
"regex",
"reqwest",
"serde",
"serde_json",
"tarpc",
"test-strategy",
"thiserror 1.0.69",
"tokio",
"tower-http",
"tower-http 0.5.2",
"tracing",
"tracing-subscriber",
"url",
@ -2517,13 +2662,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.1"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.9",
"regex-automata 0.4.11",
"regex-syntax 0.8.5",
]
@ -2538,9 +2683,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.9"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
dependencies = [
"aho-corasick",
"memchr",
@ -2559,6 +2704,60 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "reqwest"
version = "0.12.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tower",
"tower-http 0.6.6",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rs-leveldb"
version = "0.1.5"
@ -2594,6 +2793,39 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "rustls"
version = "0.23.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.21"
@ -2915,6 +3147,9 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
@ -2941,6 +3176,27 @@ dependencies = [
"windows",
]
[[package]]
name = "system-configuration"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "systemstat"
version = "0.2.5"
@ -3229,6 +3485,16 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-serde"
version = "0.8.0"
@ -3301,6 +3567,24 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower-http"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
@ -3507,6 +3791,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "twenty-first"
version = "0.50.0"
@ -3589,6 +3879,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.4"
@ -3639,6 +3935,15 @@ dependencies = [
"libc",
]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@ -3680,6 +3985,19 @@ 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"
@ -3712,6 +4030,16 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -3819,6 +4147,17 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-registry"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
dependencies = [
"windows-link",
"windows-result 0.3.4",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.1.2"

View File

@ -2,9 +2,20 @@
name = "neptune-explorer"
version = "0.1.0"
edition = "2021"
default-run = "neptune-explorer"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "high_rate_attack"
path = "src/bin/high_rate_attack.rs"
required-features = ["attacks"]
[[bin]]
name = "scraper"
path = "src/bin/scraper.rs"
required-features = ["attacks"]
[dependencies]
axum = { version = "0.7.9", features = ["macros"] }
serde = { version = "1.0.197", features = ["derive"] }
@ -40,6 +51,11 @@ indexmap = "2.7.0"
blake3 = { version = "1.8.2", optional = true }
rand = { version = "0.9.2", optional = true }
reqwest = { version = "0.12.23", optional = true }
log = {version = "0.4.28", optional = true}
env_logger = {version = "0.11.8", optional = true}
regex = {version = "1.11.3", optional = true }
#[dev-dependencies]
test-strategy = "0.4.3"
@ -52,3 +68,4 @@ neptune-cash = { git = "https://github.com/Neptune-Crypto/neptune-core.git", bra
[features]
mock = ["dep:blake3", "dep:rand"]
attacks = ["reqwest", "log", "env_logger", "regex", "dep:rand"]

View File

@ -0,0 +1,27 @@
use reqwest::Client;
use tokio::sync::futures;
use tokio::time::Instant;
#[tokio::main]
async fn main() {
let client = Client::new();
let url = "http://127.0.0.1:3000/your_endpoint";
let num_requests = 200; // adjust as needed
let concurrency = 20; // parallel tasks
let start = Instant::now();
let futures = (0..num_requests).map(|_| {
let client = &client;
async move {
let _ = client.get(url).send().await;
}
});
futures::future::join_all(futures).await;
let elapsed = start.elapsed();
println!(
"Sent {} requests in {:.2?} (concurrency {})",
num_requests, elapsed, concurrency
);
}

103
src/bin/scraper.rs Normal file
View File

@ -0,0 +1,103 @@
use env_logger;
use log::LevelFilter;
use log::{error, info, warn};
use rand::seq::IteratorRandom;
use regex::Regex;
use reqwest::Client;
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::{signal, time};
use url::Url;
/// Scrape the explorer website when running locally.
///
/// This program maintains a dictionary of URLs, which is initially populated
/// with 'http://localhost:3000'. It fetches a random URL from the dictionary in
/// each iteration, logs positive messages if successful, extracts new URLs from
/// the response body to add to the dictionary, logs warnings or errors for
/// request failures or timeouts, sleeps for a bit, and continues until Ctrl-C
/// is pressed.
///
/// Run with:
/// `> cargo run --bin scraper`
#[tokio::main]
async fn main() {
// Initialize logger
env_logger::builder().filter_level(LevelFilter::Info).init();
let client = Client::builder()
.timeout(Duration::from_millis(300))
.build()
.expect("Failed to build HTTP client");
let root_url = "http://localhost:3000".to_string();
let urls = Arc::new(Mutex::new(HashSet::from([root_url.clone()])));
let href_regex = Regex::new(r#"<a\s+(?:[^>]*?\s+)?href=['\"](.*?)['\"]"#).unwrap();
info!("Starting fetch loop. Press Ctrl-C to stop.");
let urls_clone = Arc::clone(&urls);
let fetch_loop = async move {
loop {
// Pick a random URL safely
let url_opt = {
let urls_guard = urls_clone.lock().unwrap();
urls_guard.iter().choose(&mut rand::rng()).cloned()
};
if let Some(url) = url_opt {
match client.get(&url).send().await {
Ok(resp) => {
if resp.status().is_success() {
match resp.text().await {
Ok(text) => {
info!("Success fetching {}", url);
let mut urls_guard = urls_clone.lock().unwrap();
for cap in href_regex.captures_iter(&text) {
let href = &cap[1];
if let Ok(parsed_url) =
Url::parse(&[&root_url.clone(), href].concat())
{
let normalized = parsed_url.as_str();
if urls_guard.insert(normalized.to_owned()) {
info!(
"Added new URL to dictionary: {}",
normalized
);
}
}
}
}
Err(e) => {
warn!("Failed to read response body from {}: {}", url, e);
}
}
} else {
warn!("Non-success status {} from {}", resp.status(), url);
}
}
Err(err) => {
if err.is_timeout() {
warn!("Timeout fetching {}", url);
} else {
error!("Error fetching {}: {}", url, err);
}
}
}
} else {
warn!("URL dictionary is empty, no URL to fetch");
}
time::sleep(Duration::from_millis(500)).await;
}
};
tokio::select! {
_ = fetch_loop => {}, // This runs indefinitely unless stopped
_ = signal::ctrl_c() => {
info!("Ctrl-C received, stopping...");
}
}
}