use std::net::SocketAddr;
use std::time::Instant;
use http_body_util::combinators::BoxBody;
use http_body_util::{BodyExt, Empty, Full};
use hyper::body::{Bytes, Incoming};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client;
use hyper_util::rt::{TokioExecutor, TokioIo};
use tokio::net::TcpListener;
use waf_core::{
Config, WafMode,
};
use waf_proxy::Proxy;
type ReqBody = BoxBody<Bytes, hyper::Error>;
const SAMPLES: usize = 30_000;
const WARMUP: usize = 2_000;
const CANDIDATE: &str = "url=http%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%2F";
const BENIGN: &str = "q=hello";
fn empty_body() -> ReqBody {
Empty::new().map_err(|never| match never {}).boxed()
}
fn full_body(b: &'static [u8]) -> ReqBody {
Full::new(Bytes::from_static(b)).map_err(|never| match never {}).boxed()
}
async fn start_echo() -> SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
loop {
let Ok((stream, _)) = listener.accept().await else { break };
tokio::spawn(async move {
let io = TokioIo::new(stream);
let svc = service_fn(|_req: Request<Incoming>| async {
Ok::<_, hyper::Error>(Response::new(full_body(b"ok")))
});
let _ = http1::Builder::new().serve_connection(io, svc).await;
});
}
});
addr
}
fn cfg(backend: SocketAddr, mode: WafMode) -> Config {
let mut c = Config::default();
c.proxy.listen = "127.0.0.1:0".parse().unwrap();
c.proxy.backend = format!("http://{backend}");
c.waf.mode = mode;
c.waf.paranoia_level = 3;
c
}
async fn one_status(client: &Client<HttpConnector, ReqBody>, addr: SocketAddr, query: &str) -> u16 {
client
.request(Request::builder().uri(format!("http://{addr}/?{query}")).body(empty_body()).unwrap())
.await
.unwrap()
.status()
.as_u16()
}
async fn probe(client: &Client<HttpConnector, ReqBody>, addr: SocketAddr, query: &str) -> Vec<u64> {
let uri = format!("http://{addr}/?{query}");
let mut lat = Vec::with_capacity(SAMPLES);
let mut errors = 0usize;
for _ in 0..SAMPLES {
let t = Instant::now();
let ok = client
.request(Request::builder().uri(uri.clone()).body(empty_body()).unwrap())
.await
.map(|r| r.status().is_success())
.unwrap_or(false);
if ok {
lat.push(t.elapsed().as_nanos() as u64);
} else {
errors += 1;
}
}
if errors > 0 {
eprintln!(" warning: {errors} failed requests on {query}");
}
lat.sort_unstable();
lat
}
fn pct(sorted: &[u64], p: f64) -> u64 {
if sorted.is_empty() {
return 0;
}
let rank = (p / 100.0 * sorted.len() as f64).ceil() as usize;
sorted[rank.saturating_sub(1).min(sorted.len() - 1)]
}
#[tokio::main]
async fn main() {
let backend = start_echo().await;
let client: Client<HttpConnector, ReqBody> =
Client::builder(TokioExecutor::new()).build(HttpConnector::new());
let blocking = Proxy::bind(&cfg(backend, WafMode::Blocking)).await.unwrap();
let block_addr = blocking.local_addr().unwrap();
tokio::spawn(blocking.run());
let cand_status = one_status(&client, block_addr, CANDIDATE).await;
let benign_status = one_status(&client, block_addr, BENIGN).await;
println!("candidacy proof (blocking WAF): candidate→{cand_status} (expect 403), benign→{benign_status} (expect 200)");
assert_eq!(cand_status, 403, "candidate must reach inspection (blocked in blocking mode)");
assert_eq!(benign_status, 200, "benign must not trigger");
let waf = Proxy::bind(&cfg(backend, WafMode::DetectionOnly)).await.unwrap();
let waf_addr = waf.local_addr().unwrap();
tokio::spawn(waf.run());
let pass = Proxy::bind_passthrough(&cfg(backend, WafMode::DetectionOnly)).await.unwrap();
let pass_addr = pass.local_addr().unwrap();
tokio::spawn(pass.run());
for _ in 0..WARMUP {
let _ = one_status(&client, waf_addr, CANDIDATE).await;
let _ = one_status(&client, pass_addr, CANDIDATE).await;
}
let waf_cand = probe(&client, waf_addr, CANDIDATE).await;
let pass_cand = probe(&client, pass_addr, CANDIDATE).await;
let waf_benign = probe(&client, waf_addr, BENIGN).await;
let row = |name: &str, s: &[u64]| {
println!(" {name:<22} p50 {:>7} ns p99 {:>8} ns (n={})", pct(s, 50.0), pct(s, 99.0), s.len());
};
println!("\n== e2e latency, closed-loop unloaded (loopback + echo; INFORMATIONAL, not the contract) ==");
row("WAF / candidate", &waf_cand);
row("passthrough / cand", &pass_cand);
row("WAF / benign", &waf_benign);
let p50 = |s: &[u64]| pct(s, 50.0) as i64;
let overhead_delta = p50(&waf_cand) - p50(&pass_cand); let inspect_delta = p50(&waf_cand) - p50(&waf_benign); let noise = (pct(&waf_cand, 99.0) - pct(&waf_cand, 50.0)) as i64; println!("\nWAF latency delta (median):");
println!(" WAF − passthrough (candidate) : {overhead_delta:+} ns (proxy-path + normalize + detect, noise-laden)");
println!(" WAF cand − WAF benign : {inspect_delta:+} ns (pure inspection; expected ~+2–3µs)");
println!(" e2e jitter scale (p99−p50) : ~{noise} ns");
if inspect_delta.abs() < noise {
println!("\nVERDICT: the pure-inspection signal (~µs) is BELOW this box's e2e noise floor — the delta ({inspect_delta:+} ns) is within jitter (~{noise} ns), and can come out negative purely from noise.");
println!("This is the expected in-process outcome and exactly DEC-C2: e2e is loopback/upstream-dominated, so this number CANNOT be the contract.");
println!("→ Contract stays the isolated ~2µs bench (a)/(d). A trustworthy e2e overhead curve needs oha on a QUIET, separate-process box (the deployment caveat, like git/CI on (d)).");
} else {
println!("\nVERDICT: pure-inspection delta {inspect_delta:+} ns resolves ABOVE jitter (~{noise} ns) — clean enough to read on this box.");
}
println!("\nThe hard guarantee that holds regardless of resolution: the candidacy guard (candidate→403, benign→200) proves the WAF really inspects — the §13 bite, noise-immune.");
}