diff --git a/src/latency.rs b/src/latency.rs index 1e2b08e..dc2f7ad 100644 --- a/src/latency.rs +++ b/src/latency.rs @@ -73,13 +73,17 @@ pub async fn run( results } +/// Timed round-trips on the warm connection; the fastest is reported to shed +/// transient network jitter. +const SAMPLES: usize = 3; + /// Measure the steady-state round-trip latency through the node via `ip`. /// /// Building the tunnel and the inner TLS is a one-time cost a live proxy /// session pays only once, so we exclude it: set up the connection, send a -/// throwaway warm-up request, then time a second request on the warm -/// connection. That matches what clients like v2rayN report — the per-request -/// RTT, not the cold connect-and-handshake total. +/// throwaway warm-up request, then time several follow-up requests on the warm +/// connection and keep the fastest. That matches what clients like v2rayN +/// report — the per-request RTT, not the cold connect-and-handshake total. pub async fn measure(node: &VlessNode, ip: IpAddr, timeout: Duration) -> Result { let attempt = tokio::time::timeout(timeout, async { let tunnel = vless::connect(node, ip, TARGET_HOST, TARGET_PORT).await?; @@ -89,12 +93,24 @@ pub async fn measure(node: &VlessNode, ip: IpAddr, timeout: Duration) -> Result< let mut stream = vless::tls_connector().connect(sni, tunnel).await?; // Warm-up: absorbs the tunnel + TLS setup cost (and TCP slow start). + // Timed too, so it can stand in as the result if the node won't honour + // keep-alive for the follow-ups, rather than failing the IP outright. + let warmup_start = Instant::now(); request_once(&mut stream).await?; + let warmup_rtt = warmup_start.elapsed(); - // Timed: pure request round-trip on the established connection. - let started = Instant::now(); - request_once(&mut stream).await?; - Ok(started.elapsed()) + // Fastest of several round-trips on the warm connection. Stop at the + // first reuse failure and keep whatever samples we did gather. + let mut best: Option = None; + for _ in 0..SAMPLES { + let started = Instant::now(); + if request_once(&mut stream).await.is_err() { + break; + } + let rtt = started.elapsed(); + best = Some(best.map_or(rtt, |b| b.min(rtt))); + } + Ok(best.unwrap_or(warmup_rtt)) }) .await;