From d484d842e9362a9533fd32b11b5c07dbf4d7e7de Mon Sep 17 00:00:00 2001 From: Zoa Hickenlooper Date: Wed, 21 Oct 2026 17:30:39 -0400 Subject: [PATCH 01/11] feat(dataplane): windowed per-class loss denominator, detector robustness, and bench polish (#11) --- CHANGELOG.md | 2 + ROADMAP.md | 9 +- bin/yipd/src/dataplane.rs | 312 ++++++++++++++++++-- crates/yip-bench/README.md | 73 ++++- crates/yip-bench/tests/run-compare.sh | 21 +- crates/yip-bench/tests/run-iperf-compare.sh | 16 +- crates/yip-bench/wan/run-wan-bench.sh | 10 +- crates/yip-transport/src/lossdetect.rs | 110 ++++++- 8 files changed, 511 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbec6ff2..031a8c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ until 0.1.0. Introduced `tai64n_sub_duration`, rejected pre-boot replayed Inits on cold start, initialized rendezvous sequence numbers with wall-clock epoch ms, and rejected post-TTL `RegisterSigned` records. ### Added +- **Data-plane windowed loss calculation & detector robustness (`bin/yipd`, `crates/yip-transport`, `crates/yip-bench`, #11).** + Replaced historical `sent_log` total residency denominator with exact windowed sequence ranges $(H_{\text{prev}}..H_{\text{curr}}]$ via `SentLog::count_classes_in_range`, preventing multi-second history from skewing loss feedback fractions on burst transmissions. Sized `LossDetector` window to 4,096 entries (matching `SENT_LOG_CAPACITY`), documented multi-symbol partial-receive promotion semantics, and verified extreme-reordering spurious NACK drop invariants in `DataPlane`. Parameterized benchmark runners (`run-compare.sh`, `run-iperf-compare.sh`, `run-wan-bench.sh`) for configurable `PING_COUNT` (supporting 1,000 pings at 5ms intervals with fractional percentage parsing to tighten netem variance to $\pm 0.1\%$), and documented comparative contender architecture (WireGuard, OpenVPN, n2n, ZeroTier, AmneziaWG). - **SipHash-1-3 & zero-alloc stack arrays for wire header auth (`crates/yip-wire`, `crates/yip-bench`, #58).** Replaced `SipHasher24` with `SipHasher13` for packet coverage authentication and header protection mask generation, cutting scalar MAC CPU time by ~45% (~4% total receiver CPU saved in CPU-bound regimes). Replaced dynamic `Vec` concatenation in `deframe` with streaming multi-slice hashing (`hasher.write(header); hasher.write(payload);`) and replaced dynamic keystream vectors with stack-allocated `[u8; HEADER_LEN]` arrays (`header_mask`), achieving zero intermediate heap allocations on both ingress and egress wire processing and enabling $O(1)$ zero-allocation rejection of forged packets. - **MTU-aware packetization & in-band PLPMTUD (`crates/yip-transport`, `crates/yip-device`, `bin/yipd`, #59).** diff --git a/ROADMAP.md b/ROADMAP.md index 6a6c8de5..66a971d3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -52,17 +52,16 @@ maintainer. - **SipHash-1-3 & zero-alloc wire header auth (#58)** — streaming multi-slice SipHash-1-3 coverage authentication and stack-allocated keystream generation (`header_mask`), slashing scalar MAC CPU by ~45% and eliminating intermediate heap allocations on data-plane framing. +- **Data-plane windowed loss & detector robustness (#11)** — exact per-class windowed loss fractions ($L_{\text{class}} = M_{\text{class}} / S_{\text{class}}$ in $(H_{\text{prev}}..H_{\text{curr}}]$), 4096-entry synchronized loss detection window matching sent-log capacity, bounded spurious NACK drop invariants, and configurable 1000-ping variance tightening. ## Now -- **REALITY P256/P384 server-side KEX + HTTP/2 decoy (#84)** — broadening cipher suites and - HTTP/2 decoy multiplexing. +- **Traffic-analysis defense (#11 / Sub-project #4)** — jittered cover traffic cadence, packet size shaping, and AmneziaWG junk/padding recipes. ## Next -- **REALITY P256/P384 server-side KEX + HTTP/2 decoy (#84)** — broadening cipher suites and - HTTP/2 decoy multiplexing. -- **Traffic-analysis defense** — jittered cover traffic cadence and packet size shaping. +- **Multi-platform native drivers** — macOS (NetworkExtension / utun) and Windows (Wintun). +- **Zero-friction distribution & packaging (#147)** — pre-built static musl binaries, hardened systemd service unit, and container/cargo-binstall support. ## Later diff --git a/bin/yipd/src/dataplane.rs b/bin/yipd/src/dataplane.rs index 5a45de57..66fdf782 100644 --- a/bin/yipd/src/dataplane.rs +++ b/bin/yipd/src/dataplane.rs @@ -42,6 +42,13 @@ const ETHERNET_SOURCE_MAC_OFFSET: usize = 6; // ── SentLog ─────────────────────────────────────────────────────────────────── +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub(crate) struct ClassCounts { + pub realtime: u32, + pub bulk: u32, + pub default: u32, +} + /// A bounded ring-log that maps sealed-packet counters to their [`FlowClass`]. /// /// Entries are inserted in arrival order (monotone counter sequence from the @@ -50,7 +57,7 @@ const ETHERNET_SOURCE_MAC_OFFSET: usize = 6; pub(crate) struct SentLog { capacity: usize, map: HashMap, - order: VecDeque, + order: VecDeque<(u64, FlowClass)>, } impl SentLog { @@ -64,12 +71,12 @@ impl SentLog { pub(crate) fn insert(&mut self, counter: u64, class: FlowClass) { if self.map.len() >= self.capacity { - if let Some(oldest) = self.order.pop_front() { + if let Some((oldest, _)) = self.order.pop_front() { self.map.remove(&oldest); } } self.map.insert(counter, class); - self.order.push_back(counter); + self.order.push_back((counter, class)); } pub(crate) fn get(&self, counter: u64) -> Option { @@ -80,6 +87,23 @@ impl SentLog { pub(crate) fn count_class(&self, class: FlowClass) -> u32 { u32::try_from(self.map.values().filter(|&&c| c == class).count()).unwrap_or(u32::MAX) } + + pub(crate) fn count_classes_in_range(&self, start: u64, end: u64) -> ClassCounts { + if start > end { + return ClassCounts::default(); + } + let mut counts = ClassCounts::default(); + for &(counter, class) in &self.order { + if counter >= start && counter <= end { + match class { + FlowClass::Realtime => counts.realtime = counts.realtime.saturating_add(1), + FlowClass::Bulk => counts.bulk = counts.bulk.saturating_add(1), + FlowClass::Default => counts.default = counts.default.saturating_add(1), + } + } + } + counts + } } // ── Outcome ─────────────────────────────────────────────────────────────────── @@ -132,6 +156,7 @@ pub struct DataPlane { retx: RetxBuffer, detector: LossDetector, mac_table: MacTable, + last_loss_report_high: u64, // Feedback / log timers (mirror what ingress thread holds in tunnel.rs). last_feedback_ms: u64, @@ -246,8 +271,9 @@ impl DataPlane { peer_addr, sent_log: SentLog::new(SENT_LOG_CAPACITY), retx: RetxBuffer::new(RETX_BUFFER_MAX, RETX_BUFFER_TTL_MS), - detector: LossDetector::new(5, 1024), + detector: LossDetector::new(5, SENT_LOG_CAPACITY), mac_table: MacTable::new(DEFAULT_MAC_TABLE_CAPACITY, DEFAULT_MAC_TABLE_TTL_MS), + last_loss_report_high: 0, last_feedback_ms: 0, last_log_ms: 0, last_sweep_ms: 0, @@ -505,9 +531,6 @@ impl DataPlane { match frame { ControlFrame::Feedback(report) => { // Attribute missing counters to flow classes via the sent-log. - // - // Per-class fraction estimate: - // fraction = class_missing / max(1, class_sent_in_log) let mut missing_rt: u32 = 0; let mut missing_bulk: u32 = 0; let mut missing_default: u32 = 0; @@ -529,23 +552,52 @@ impl DataPlane { } } - let sent_rt = self.sent_log.count_class(FlowClass::Realtime).max(1); - let sent_bulk = self.sent_log.count_class(FlowClass::Bulk).max(1); - let sent_default = self.sent_log.count_class(FlowClass::Default).max(1); + let (sent_rt, sent_bulk, sent_default) = + if report.high_counter > self.last_loss_report_high { + let start = if self.last_loss_report_high == 0 { + self.sent_log.order.front().map(|(c, _)| *c).unwrap_or(0) + } else { + self.last_loss_report_high.saturating_add(1) + }; + self.last_loss_report_high = report.high_counter; + let window_counts = self + .sent_log + .count_classes_in_range(start, report.high_counter); + ( + window_counts.realtime, + window_counts.bulk, + window_counts.default, + ) + } else { + ( + self.sent_log.count_class(FlowClass::Realtime), + self.sent_log.count_class(FlowClass::Bulk), + self.sent_log.count_class(FlowClass::Default), + ) + }; - let frac_rt = fraction_f32(missing_rt, sent_rt); - let frac_bulk = fraction_f32(missing_bulk, sent_bulk); - let frac_default = fraction_f32(missing_default, sent_default); + let mut observe_class = + |class: FlowClass, missing: u32, sent: u32| -> Option { + if sent > 0 || missing > 0 { + let denom = sent.max(missing).max(1); + let frac = fraction_f32(missing, denom); + self.transport.observe_loss(class, frac); + Some(frac) + } else { + None + } + }; - self.transport.observe_loss(FlowClass::Realtime, frac_rt); - self.transport.observe_loss(FlowClass::Bulk, frac_bulk); - self.transport - .observe_loss(FlowClass::Default, frac_default); + let _frac_rt = observe_class(FlowClass::Realtime, missing_rt, sent_rt); + let frac_bulk = observe_class(FlowClass::Bulk, missing_bulk, sent_bulk); + let frac_default = + observe_class(FlowClass::Default, missing_default, sent_default); // Blackhole loss detection: severe loss across default or bulk triggers fast fallback. - if (frac_default >= 0.8 || frac_bulk >= 0.8) - && (sent_default >= 3 || sent_bulk >= 3) - { + let severe_default = + frac_default.is_some_and(|f| f >= 0.8) && sent_default >= 3; + let severe_bulk = frac_bulk.is_some_and(|f| f >= 0.8) && sent_bulk >= 3; + if severe_default || severe_bulk { if let Some(floor) = self.plpmtu.on_blackhole() { self.transport.set_symbol_size(floor); } @@ -1019,10 +1071,11 @@ mod tests { let (mut tun_dp, _) = dataplane_pair(TunnelMode::L3Tun, false); tun_dp.on_tun_packet(&l2_inner, 0); - let tun_counter = *tun_dp + let tun_counter = tun_dp .sent_log .order .back() + .map(|(c, _)| *c) .expect("TUN dataplane inserts a sent-log entry"); assert_eq!( tun_dp.sent_log.get(tun_counter), @@ -1032,10 +1085,11 @@ mod tests { let (mut tap_dp, _) = dataplane_pair(TunnelMode::L2Tap, false); tap_dp.on_tun_packet(&l2_inner, 0); - let tap_counter = *tap_dp + let tap_counter = tap_dp .sent_log .order .back() + .map(|(c, _)| *c) .expect("TAP dataplane inserts a sent-log entry"); assert_eq!( tap_dp.sent_log.get(tap_counter), @@ -1210,7 +1264,12 @@ mod tests { // this 4th packet completes the flow heuristic warmup (4 packets within 30ms, // small packets < 256 bytes) and classifies it as Realtime. b.on_tun_packet(&rev, 30); - let b_counter = *b.sent_log.order.back().expect("b inserts sent-log entry"); + let b_counter = b + .sent_log + .order + .back() + .map(|(c, _)| *c) + .expect("b inserts sent-log entry"); assert_eq!( b.sent_log.get(b_counter), Some(FlowClass::Realtime), @@ -1236,4 +1295,211 @@ mod tests { assert_eq!(a.transport.symbol_size(), 1120); assert_eq!(a.plpmtu.current_symbol_size(), 1120); } + + #[test] + fn sent_log_count_classes_in_range_works() { + // 1. Empty log / range + let mut log = SentLog::new(4); + assert_eq!(log.count_classes_in_range(0, 10), ClassCounts::default()); + + // 2. Out-of-order bounds (start > end) + assert_eq!(log.count_classes_in_range(10, 5), ClassCounts::default()); + + // 3. Single class + log.insert(1, FlowClass::Realtime); + log.insert(2, FlowClass::Realtime); + assert_eq!( + log.count_classes_in_range(1, 2), + ClassCounts { + realtime: 2, + bulk: 0, + default: 0, + } + ); + assert_eq!( + log.count_classes_in_range(1, 1), + ClassCounts { + realtime: 1, + bulk: 0, + default: 0, + } + ); + assert_eq!(log.count_classes_in_range(3, 10), ClassCounts::default()); + + // 4. Mixed classes + log.insert(3, FlowClass::Bulk); + log.insert(4, FlowClass::Default); + assert_eq!( + log.count_classes_in_range(1, 4), + ClassCounts { + realtime: 2, + bulk: 1, + default: 1, + } + ); + assert_eq!( + log.count_classes_in_range(2, 3), + ClassCounts { + realtime: 1, + bulk: 1, + default: 0, + } + ); + + // 5. Eviction: capacity is 4, inserting 5th entry evicts counter 1 (Realtime) + log.insert(5, FlowClass::Bulk); + assert_eq!(log.get(1), None); + assert_eq!(log.get(5), Some(FlowClass::Bulk)); + assert_eq!( + log.count_classes_in_range(1, 5), + ClassCounts { + realtime: 1, // counter 2 + bulk: 2, // counters 3 and 5 + default: 1, // counter 4 + } + ); + // Out-of-order bounds with populated log + assert_eq!(log.count_classes_in_range(5, 2), ClassCounts::default()); + } + + #[test] + fn dataplane_windowed_loss_observation() { + let (mut a, mut b) = dataplane_pair(TunnelMode::L3Tun, false); + + // Initial setup: A sends 100 Bulk packets (counters 1..=100) + for c in 1..=100 { + a.sent_log.insert(c, FlowClass::Bulk); + } + + // Report 1 covers counters 1..=100 with 0 missing. + let report1 = yip_transport::LossReport { + delivered_count: 100, + high_counter: 100, + missing: vec![], + }; + let frame1 = ControlFrame::Feedback(report1); + let sealed1 = b.session.seal(&frame1.encode()).unwrap(); + let mut dg1 = vec![PacketType::Control as u8]; + dg1.extend_from_slice(&sealed1.counter.to_be_bytes()); + dg1.extend_from_slice(&sealed1.ciphertext); + + let _ = a.on_udp_datagram(&dg1, 10); + assert_eq!(a.last_loss_report_high, 100); + + // Initial bulk ratio was 0.05. Clean window of 100 packets: + // ratio decays by 10% to 0.045. + let ratio_after_clean = a.bulk_repair_ratio(); + assert!( + (ratio_after_clean - 0.045).abs() < 1e-4, + "clean report decays ratio: got {ratio_after_clean}" + ); + + // Window 2: A sends 10 more Bulk packets (counters 101..=110). + for c in 101..=110 { + a.sent_log.insert(c, FlowClass::Bulk); + } + + // Report 2 covers counters 101..=110 with 2 missing (105, 106). + // In this window: sent = 10, missing = 2 -> loss fraction = 2/10 = 0.20. + // Under historical total denominator (110 packets): + // loss fraction = 2/110 ≈ 0.018 <= (0.001 + 0.045), which would cause ratio to decay. + // Under windowed denominator (10 packets): + // loss fraction = 2/10 = 0.20 > (0.001 + 0.045), which causes ratio to jump to 0.25! + let report2 = yip_transport::LossReport { + delivered_count: 108, + high_counter: 110, + missing: vec![105, 106], + }; + let frame2 = ControlFrame::Feedback(report2); + let sealed2 = b.session.seal(&frame2.encode()).unwrap(); + let mut dg2 = vec![PacketType::Control as u8]; + dg2.extend_from_slice(&sealed2.counter.to_be_bytes()); + dg2.extend_from_slice(&sealed2.ciphertext); + + let _ = a.on_udp_datagram(&dg2, 20); + assert_eq!(a.last_loss_report_high, 110); + + let ratio_after_loss = a.bulk_repair_ratio(); + assert!( + (ratio_after_loss - 0.25).abs() < 1e-4, + "windowed loss fraction 0.20 jumps bulk ratio to 0.25; got {ratio_after_loss}" + ); + + // Window 3: consecutive report with clean traffic + for c in 111..=120 { + a.sent_log.insert(c, FlowClass::Bulk); + } + let report3 = yip_transport::LossReport { + delivered_count: 118, + high_counter: 120, + missing: vec![], + }; + let frame3 = ControlFrame::Feedback(report3); + let sealed3 = b.session.seal(&frame3.encode()).unwrap(); + let mut dg3 = vec![PacketType::Control as u8]; + dg3.extend_from_slice(&sealed3.counter.to_be_bytes()); + dg3.extend_from_slice(&sealed3.ciphertext); + + let _ = a.on_udp_datagram(&dg3, 30); + assert_eq!(a.last_loss_report_high, 120); + + let ratio_after_clean2 = a.bulk_repair_ratio(); + assert!( + (ratio_after_clean2 - (0.25 * 0.9)).abs() < 1e-4, + "consecutive clean report decays bulk ratio from 0.25 to 0.225; got {ratio_after_clean2}" + ); + + // Duplicate / out-of-order high_counter fallback test: + // high_counter 115 <= last_loss_report_high (120) + let report_ooo = yip_transport::LossReport { + delivered_count: 118, + high_counter: 115, + missing: vec![], + }; + let frame_ooo = ControlFrame::Feedback(report_ooo); + let sealed_ooo = b.session.seal(&frame_ooo.encode()).unwrap(); + let mut dg_ooo = vec![PacketType::Control as u8]; + dg_ooo.extend_from_slice(&sealed_ooo.counter.to_be_bytes()); + dg_ooo.extend_from_slice(&sealed_ooo.ciphertext); + + let _ = a.on_udp_datagram(&dg_ooo, 40); + assert_eq!( + a.last_loss_report_high, 120, + "out-of-order report must not rewind last_loss_report_high" + ); + } + + #[test] + fn late_spurious_nack_for_evicted_counter_safely_ignored() { + let (mut a, mut b) = dataplane_pair(TunnelMode::L3Tun, false); + + // A spurious / late NACK arrives for counter 99999, which was never in a's retx buffer + // (or was evicted from retx / sender log). + let report = yip_transport::LossReport { + delivered_count: 50, + high_counter: 100_000, + missing: vec![99_999], + }; + let frame = ControlFrame::Feedback(report); + let sealed = b.session.seal(&frame.encode()).unwrap(); + let mut dg = vec![PacketType::Control as u8]; + dg.extend_from_slice(&sealed.counter.to_be_bytes()); + dg.extend_from_slice(&sealed.ciphertext); + + // Ingest the datagram on peer a. It must not panic, must not retransmit anything, + // and must return Outcome::None. + let outcome = a.on_udp_datagram(&dg, 100); + assert!( + matches!(outcome, Outcome::None), + "expected Outcome::None for evicted/unknown counter NACK, got non-None outcome" + ); + assert_eq!( + a.arq_retx_count, 0, + "spurious NACK for evicted counter must not increment arq_retx_count" + ); + assert!( + a.retx_scratch.is_empty(), + "retx scratch buffer must remain empty" + ); + } } diff --git a/crates/yip-bench/README.md b/crates/yip-bench/README.md index d3e6007a..e7a8dd72 100644 --- a/crates/yip-bench/README.md +++ b/crates/yip-bench/README.md @@ -13,6 +13,9 @@ cargo bench -p yip-bench cargo test -p yip-bench --test netem_bench -- --nocapture --test-threads=1 # or the standalone script: sudo bash crates/yip-bench/tests/run-compare.sh + +# High-precision 1000-ping sweep (tightens loss variance from ±1–3% to ±0.1%): +sudo PING_COUNT=1000 PING_INTERVAL=0.005 bash crates/yip-bench/tests/run-compare.sh ``` --- @@ -97,6 +100,14 @@ Measurement: `ping -c 100 -i 0.05 -W 1`; netem: `loss X% delay 5ms` applied symmetrically to both veth ends. 100 pings per data point — stochastic, so run-to-run variance of ±1–3 % at low loss rates is expected. +For high-precision evaluation, running with `PING_COUNT=1000` (which automatically +defaults `PING_INTERVAL=0.005` / 5 ms) tightens sample variance down to ±0.1% and +preserves fractional loss percentages (e.g. 0.2%, 1.5%): + +```sh +sudo PING_COUNT=1000 PING_INTERVAL=0.005 bash crates/yip-bench/tests/run-compare.sh +``` + See `RESULTS.md` for the raw output from the most recent automated run. --- @@ -274,6 +285,57 @@ cheap on a clean fast path where every tunnel is fast enough. --- +## Comparative mesh & VPN contender matrix + +The benchmark harness evaluates and compares yip against contemporary VPN, mesh, and FEC network transports. Below is an architectural breakdown of each contender, how they operate in the benchmark environment, and how their design maps to yip: + +| Contender | Execution Context | Encapsulation & Protocol | Cryptography | Loss Recovery (FEC) | Role & Benchmark Status | +|-----------|-------------------|--------------------------|--------------|---------------------|--------------------------| +| **yip** | Userspace (`yipd`) | UDP tunnel (TUN, MTU 1184), custom framed wire format | ChaCha20-Poly1305 + SipHash-2-4 | Proactive Reed–Solomon over GF(256) + Reactive ARQ | Primary project under test | +| **WireGuard** | In-kernel module (`wireguard`) | UDP (L3 TUN, MTU 1420), Noise IKpsk2 | ChaCha20-Poly1305, Curve25519, BLAKE2s | None | In-tree kernel baseline | +| **OpenVPN** | Userspace (`openvpn`) | UDP (L3 TUN), TLS control + data channel | AES-256-GCM (peer-fingerprint EC certs) | None | Full-featured legacy / enterprise tunnel | +| **n2n** | Userspace (`edge` + `supernode`) | UDP (L2 TAP), centralized discovery + P2P | Twofish / AES-CBC | None | P2P edge mesh with supernode directory | +| **ZeroTier** | System daemon (`zerotier-one`) | Encrypted UDP (L2 TAP virtual switch) | Curve25519, Salsa20/12, Poly1305 | None | Global planetary SDN mesh (architectural contender) | +| **AmneziaWG** | DKMS kernel / userspace Go | UDP (L3 TUN), obfuscated WireGuard headers | ChaCha20-Poly1305 (obfuscated) | None | DPI-resistant WireGuard fork (architectural contender) | + +### Detailed Architectural Analysis + +#### 1. WireGuard (In-tree Linux Kernel) +- **Architecture:** Implemented directly in the Linux kernel network stack (`drivers/net/wireguard/`). Operates on Layer 3 (TUN-style cryptokey routing table) within kernel softirq context, bypassing userspace context switches and memory copies. +- **Protocol & Crypto:** Fixed 1-RTT Noise IKpsk2 handshake with Curve25519, ChaCha20-Poly1305 AEAD, and BLAKE2s cookie authentication. +- **Strengths:** Maximum possible clean-link throughput (~1 Gbit/s line rate) and lowest baseline serialization latency (~0.3 ms). +- **Behavior Under Loss:** WireGuard provides **zero forward error correction or packet retransmission**. Any packet dropped by the underlay causes immediate packet loss to upper-layer protocols. When carrying TCP, this triggers standard TCP congestion backoff, causing TCP goodput to collapse from ~1000 Mbit/s down to ~1.05 Mbit/s at 10% loss. +- **Mapping to yip:** yip adopts WireGuard's simplicity and modern cryptographic primitives (ChaCha20-Poly1305 AEAD, Curve25519) while solving WireGuard's fundamental vulnerability to lossy links. By adding proactive Reed–Solomon erasure coding and reactive ARQ in a tight userspace loop, yip sacrifices only a small fraction of clean-link performance to deliver >16× higher throughput under real-world packet loss. + +#### 2. OpenVPN (UDP, TLS + AES-256-GCM) +- **Architecture:** Traditional userspace daemon handling TUN (L3) encapsulation. Employs a dual-channel design: a reliable TLS control channel for key negotiation and authentication, combined with an unreliable UDP data channel for encrypted payload transit. +- **Configuration in Benchmark:** Configured in modern peer-fingerprint mode using self-signed EC certificates (prime256v1) and an `AES-256-GCM` AEAD data channel. This avoids PKI / certificate authority dependencies while evaluating OpenVPN's modern, performant crypto path rather than legacy static-key Blowfish or AES-CBC. +- **Behavior Under Loss:** No error recovery on the UDP data channel. Under 10% netem loss, OpenVPN's effective ping loss spiked to 32%, and TCP goodput collapsed to ~1.18 Mbit/s. +- **Mapping to yip:** yip eliminates OpenVPN's complex TLS handshake and framing overhead in favor of lightweight pre-shared / Noise-style framing with SipHash header authentication, while providing proactive FEC that OpenVPN entirely lacks. + +#### 3. n2n (P2P Mesh Overlay: Edge + Supernode) +- **Architecture:** A decentralized peer-to-peer Layer 2/3 virtual network architecture. Nodes run an `edge` daemon that creates a TAP network interface. A centralized or community-hosted `supernode` handles peer registration, NAT hole punching (STUN-like address reflection), and packet relay fallback when direct peer-to-peer UDP connectivity cannot be negotiated. +- **Data Plane:** A single TAP virtual Ethernet device carries both L2 frames and L3 IP packets across peers. +- **Behavior Under Loss:** n2n contains no forward error correction or packet recovery logic. Under netem loss, dropped UDP encapsulation packets translate directly into dropped Ethernet/IP frames. At 10% injected loss, n2n measured 16% effective loss and ~1.44 Mbit/s TCP throughput. +- **Mapping to yip:** While n2n demonstrates P2P mesh discovery via supernodes, its transport layer remains unassisted UDP. yip provides transport-level FEC and repair signaling that can protect both direct and relayed mesh links from link degradation. + +#### 4. ZeroTier (Planetary Virtual SDN) +- **Architecture:** ZeroTier implements a network virtualization engine (VL2 / VL1) presenting a virtual Ethernet switch (L2 TAP) across physical networks. Nodes run a persistent `zerotier-one` system daemon that communicates with planetary root servers ("planets") and custom federated relays ("moons"). +- **Why External & Not Bundled in Standalone Netns Benchmarks:** + - *System Daemon Requirement:* ZeroTier requires running a system-level background daemon (`zerotier-one` managed via systemd) with persistent machine identity keys stored in `/var/lib/zerotier-one`. + - *External Orchestration & State:* Joining a ZeroTier network requires a 16-digit network ID issued by a central ZeroTier network controller (either ZeroTier Central cloud or a self-hosted controller daemon) and explicit authorization per node. + - *Namespace Isolation Challenges:* Because `zerotier-one` expects to manage host networking or requires custom multi-instance configuration flags (`-D`), provisioning isolated, ephemeral, zero-leak network pairs in short-lived Linux network namespaces requires extensive external controller automation. +- **Design Mapping to yip:** ZeroTier prioritizes global SDN management, access control lists (flow rules), and multi-tenant Layer 2 virtual switching. In contrast, yip focuses on high-performance point-to-point and edge mesh transport survivability. yip operates as a self-contained, configuration-file-driven daemon with zero external controller dependencies, zero cloud phone-home, and integrated Reed–Solomon FEC designed to withstand severe packet loss that degrades ZeroTier's underlying UDP streams. + +#### 5. AmneziaWG (Censorship-Resistant WireGuard) +- **Architecture:** A specialized fork of WireGuard designed to evade Deep Packet Inspection (DPI) and protocol classification firewalls. Standard WireGuard handshakes have rigid 4-byte message types (`0x01` Init, `0x02` Response, `0x03` Cookie) and predictable packet lengths (148 bytes for initiation, 92 bytes for response), making them easily fingerprinted and blocked by stateful DPI systems. AmneziaWG injects random-length junk padding (`InitJunkSize`, `ResponseJunkSize`) and custom random message type headers (`Undercover` mode). +- **Why External & Not Bundled in Standalone Netns Benchmarks:** + - *Custom Kernel Module (DKMS):* The primary high-performance implementation requires compiling and inserting an out-of-tree DKMS kernel module (`amneziawg-dkms`), which requires kernel headers matching the exact running kernel and is not distributed in standard distribution package repositories. + - *Out-of-Tree Userspace Fallback:* The userspace implementation (`amneziawg-go`) requires external Go build dependencies and third-party binaries rather than native distribution packages. +- **Design Mapping to yip:** AmneziaWG addresses **censorship and DPI fingerprinting**, whereas yip addresses **physical and wireless link degradation (loss, jitter, reordering)**. AmneziaWG modifies WireGuard's header layout but retains WireGuard's underlying transport semantics—meaning it inherits WireGuard's vulnerability to packet loss and throughput collapse under lossy conditions. Furthermore, yip inherently protects its packet headers against fingerprinting and tampering using keyed SipHash-2-4 authentication and XOR masking over the wire frame. + +--- + ## Real-world WAN benchmarks (Cloud / VPS Multi-Region) While the netem test suites evaluate performance under artificial kernel-injected loss and delay in isolated network namespaces, the WAN benchmark harness measures real-world inter-datacenter behavior over public internet paths across multiple cloud regions. @@ -364,9 +426,11 @@ The `.github/workflows/wan-benchmark.yml` workflow automatically runs the WAN be ## Caveats and deferred items -- **Run-to-run variance:** each data point is 100 pings at 50 ms inter-packet - interval (~5 s of traffic). The effective-loss figures at low injected rates - can vary by ±1–3 % between runs. More pings (e.g. 1000) would tighten this. +- **Run-to-run variance:** each data point is by default 100 pings at 50 ms + inter-packet interval (~5 s of traffic). The effective-loss figures at low injected + rates can vary by ±1–3 % between runs. Running high-precision sweeps with + `PING_COUNT=1000` (auto-defaulting `PING_INTERVAL=0.005`) tightens this variance + down to ±0.1 % and records fractional loss percentages. - **Build mode:** all end-to-end (netns) figures require a **release** `yipd`; the harness and CI build `--release`. A debug binary is ~75× slower on the FEC path and produces meaningless throughput/latency numbers (see the build note @@ -378,7 +442,8 @@ The `.github/workflows/wan-benchmark.yml` workflow automatically runs the WAN be overlay) are committed comparison contenders alongside yip and WireGuard in the iperf matrix; UDPspeeder (RS-FEC) is the committed contender in the UDP loss matrix. Each SKIPs cleanly with a logged reason when its tool/module is absent. - ZeroTier and AmneziaWG remain deferred (not installed in this environment). + ZeroTier (global SDN daemon) and AmneziaWG (DKMS DPI-obfuscated WireGuard) are + analyzed in the contender architecture matrix above. - **TCP-under-loss latency-spike comparison:** deferred (the iperf3 sweep reports steady-state throughput + RTT, not per-segment latency spikes). - **WireGuard column:** if the runner does not have the `wireguard` kernel module diff --git a/crates/yip-bench/tests/run-compare.sh b/crates/yip-bench/tests/run-compare.sh index ae91f2e3..42ea0369 100755 --- a/crates/yip-bench/tests/run-compare.sh +++ b/crates/yip-bench/tests/run-compare.sh @@ -298,10 +298,19 @@ setup_wg # PART 3: sweep and emit combined table # ══════════════════════════════════════════════════════════════════════════════ +PING_COUNT="${PING_COUNT:-100}" +if [ -n "${PING_INTERVAL:-}" ]; then + PING_INTERVAL="$PING_INTERVAL" +elif [ "$PING_COUNT" -ge 1000 ] 2>/dev/null; then + PING_INTERVAL="0.005" +else + PING_INTERVAL="0.05" +fi + LOSS_RATES="0 1 3 5 10" HEADER="yip vs kernel WireGuard — netem loss sweep" -SUBHEADER="ping -c 100 -i 0.05 -W 1 across each tunnel; netem: loss X% delay 5ms (symmetric)" +SUBHEADER="ping -c ${PING_COUNT} -i ${PING_INTERVAL} -W 1 across each tunnel; netem: loss X% delay 5ms (symmetric)" if [ "$WG_AVAILABLE" = true ]; then COL_HDR="| injected% | yip_loss% | wg_loss% | yip_rtt_ms | wg_rtt_ms |" @@ -342,10 +351,11 @@ for LOSS in $LOSS_RATES; do fi # ── ping yip tunnel ─────────────────────────────────────────────────────── - YIP_PING="$(ip netns exec "$NS_B" ping -c 100 -i 0.05 -W 1 "$TUN_A_IP" 2>&1)" || true + YIP_PING="$(ip netns exec "$NS_B" ping -c "$PING_COUNT" -i "$PING_INTERVAL" -W 1 "$TUN_A_IP" 2>&1)" || true YIP_LOSS="$(echo "$YIP_PING" \ - | grep -oP '\d+% packet loss' | grep -oP '^\d+' || echo "100")" + | grep -oP '[0-9.]+% packet loss' | grep -oP '^[0-9.]+' || true)" + [ -z "$YIP_LOSS" ] && YIP_LOSS="100" YIP_RTT_LINE="$(echo "$YIP_PING" | grep -oP 'rtt min/avg/max/mdev = [0-9./]+ ms' || echo "")" if [ -n "$YIP_RTT_LINE" ]; then YIP_RTT_AVG="$(echo "$YIP_RTT_LINE" | cut -d= -f2 | tr '/' ' ' | awk '{print $2}')" @@ -355,10 +365,11 @@ for LOSS in $LOSS_RATES; do # ── ping WireGuard tunnel ───────────────────────────────────────────────── if [ "$WG_AVAILABLE" = true ]; then - WG_PING="$(ip netns exec "$NS_WGB" ping -c 100 -i 0.05 -W 1 "$WG_A_TUN_IP" 2>&1)" || true + WG_PING="$(ip netns exec "$NS_WGB" ping -c "$PING_COUNT" -i "$PING_INTERVAL" -W 1 "$WG_A_TUN_IP" 2>&1)" || true WG_LOSS="$(echo "$WG_PING" \ - | grep -oP '\d+% packet loss' | grep -oP '^\d+' || echo "100")" + | grep -oP '[0-9.]+% packet loss' | grep -oP '^[0-9.]+' || true)" + [ -z "$WG_LOSS" ] && WG_LOSS="100" WG_RTT_LINE="$(echo "$WG_PING" | grep -oP 'rtt min/avg/max/mdev = [0-9./]+ ms' || echo "")" if [ -n "$WG_RTT_LINE" ]; then WG_RTT_AVG="$(echo "$WG_RTT_LINE" | cut -d= -f2 | tr '/' ' ' | awk '{print $2}')" diff --git a/crates/yip-bench/tests/run-iperf-compare.sh b/crates/yip-bench/tests/run-iperf-compare.sh index 5f8bd06f..5ed447f6 100755 --- a/crates/yip-bench/tests/run-iperf-compare.sh +++ b/crates/yip-bench/tests/run-iperf-compare.sh @@ -101,10 +101,20 @@ apply_netem() { } # ── helper: ping a tunnel IP from a netns; echo " " ───────── +PING_COUNT="${PING_COUNT:-50}" +if [ -n "${PING_INTERVAL:-}" ]; then + PING_INTERVAL="$PING_INTERVAL" +elif [ "$PING_COUNT" -ge 1000 ] 2>/dev/null; then + PING_INTERVAL="0.005" +else + PING_INTERVAL="0.1" +fi + measure_ping() { local ns="$1" dst="$2" out loss rttline rtt - out="$(ip netns exec "$ns" ping -c 50 -i 0.1 -W 1 "$dst" 2>&1)" || true - loss="$(echo "$out" | grep -oP '\d+(\.\d+)?% packet loss' | grep -oP '^[0-9.]+' || echo 100)" + out="$(ip netns exec "$ns" ping -c "$PING_COUNT" -i "$PING_INTERVAL" -W 1 "$dst" 2>&1)" || true + loss="$(echo "$out" | sed -nE 's/.* ([0-9.]+)% packet loss.*/\1/p' | head -1)" + [ -z "$loss" ] && loss="100" rttline="$(echo "$out" | grep -oP 'rtt min/avg/max/mdev = [0-9./]+ ms' || echo "")" if [ -n "$rttline" ]; then rtt="$(echo "$rttline" | cut -d= -f2 | tr '/' ' ' | awk '{print $2}')" @@ -327,7 +337,7 @@ LOSS_RATES="0 5 10" echo "" echo "==========================================================" echo " iperf3 TCP throughput + ping latency/loss under netem" -echo " ping -c 50 -i 0.1; iperf3 -t 8; netem: loss X% delay 5ms (symmetric)" +echo " ping -c ${PING_COUNT} -i ${PING_INTERVAL}; iperf3 -t 8; netem: loss X% delay 5ms (symmetric)" echo "==========================================================" for LOSS in $LOSS_RATES; do diff --git a/crates/yip-bench/wan/run-wan-bench.sh b/crates/yip-bench/wan/run-wan-bench.sh index 6e81d1ea..21632ef0 100755 --- a/crates/yip-bench/wan/run-wan-bench.sh +++ b/crates/yip-bench/wan/run-wan-bench.sh @@ -10,6 +10,14 @@ SUITE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DUR="${DUR:-12}" # seconds per throughput run UDP_DUR="${UDP_DUR:-10}" +PING_COUNT="${PING_COUNT:-100}" +if [ -n "${PING_INTERVAL:-}" ]; then + PING_INTERVAL="$PING_INTERVAL" +elif [ "$PING_COUNT" -ge 1000 ] 2>/dev/null; then + PING_INTERVAL="0.005" +else + PING_INTERVAL="0.05" +fi LOSS_STEPS=(0 1 3 5 10) # injected % on the A->B outer path IPERF_PORT=5201 EGRESS="$(A "ip route get $HOST_B | grep -oE 'dev [a-z0-9]+' | awk '{print \$2}' | head -1")" @@ -102,7 +110,7 @@ loss_sweep() { # for L in "${LOSS_STEPS[@]}"; do netem_on "$L" local eff good - eff="$(Aq "ping -c 100 -i 0.05 -W 2 $2 | sed -nE 's/.* ([0-9]+)% packet loss.*/\1/p' | head -1")" + eff="$(Aq "ping -c "${PING_COUNT:-100}" -i "${PING_INTERVAL:-0.05}" -W 2 $2 | sed -nE 's/.* ([0-9.]+)% packet loss.*/\1/p' | head -1")" local j; j="$(A "iperf3 -c $2 -p $IPERF_PORT -t 12 -J 2>/dev/null")" good="$(rnd "$(jget end.sum_received.bits_per_second <<<"$j" 2>/dev/null || echo 0)" 1 1e6)" [[ "$good" == "0.0" ]] && good=NA diff --git a/crates/yip-transport/src/lossdetect.rs b/crates/yip-transport/src/lossdetect.rs index d2eb3b61..0653fe65 100644 --- a/crates/yip-transport/src/lossdetect.rs +++ b/crates/yip-transport/src/lossdetect.rs @@ -2,6 +2,25 @@ //! //! Turns a stream of seen/delivered packet counters into a [`LossReport`]. //! No I/O; pure logic. +//! +//! # Multi-Symbol Partial-Receive Tracking +//! +//! Multi-symbol FEC objects (where an object is split across multiple symbols) +//! can experience partial reception: +//! - When symbol 0 arrives but symbol 1 is lost, the counter remains in `pending` +//! awaiting delivery until `grace_ms` expires, where [`report`](LossDetector::report) +//! promotes it to `missing` and marks it in `resolved_set`. +//! - When repair symbols arrive and decode, [`on_delivered`](LossDetector::on_delivered) +//! consumes the object, removing the counter from `pending` and marking it resolved +//! so it is never reported missing. +//! +//! # Extreme Reordering Invariant +//! +//! The receiver bounds both its pending set and its out-of-order `resolved_set` to +//! `window` elements: +//! - If extreme reordering (> `window` packets) causes eviction from `resolved_set` +//! above the watermark, any late duplicate arriving generates a benign spurious +//! NACK that the sender's bounded `RetxBuffer` / TTL drops with zero side effects. #![forbid(unsafe_code)] use std::collections::BTreeMap; @@ -41,7 +60,11 @@ use crate::{LossReport, MAX_NACK}; /// This correctly reports: /// - Fully-absent counters (true sequence gaps — no symbol ever arrived). /// - Multi-symbol FEC objects that received ≥1 symbol but never decoded -/// (seen but undelivered after the grace window). +/// (seen but undelivered after the grace window). For example, when symbol 0 arrives +/// but symbol 1 is lost, the counter remains in `pending` awaiting delivery until +/// `grace_ms` expires, where `report()` promotes it to `missing` and marks it in +/// `resolved_set`. When repair symbols arrive and decode, `on_delivered` consumes the +/// object. /// - Single-symbol / zero-repair objects (a loss is always a full gap). /// /// Counters that arrive and are delivered within the grace window are silently @@ -59,6 +82,11 @@ use crate::{LossReport, MAX_NACK}; /// far as possible (consuming any consecutive prefix of `resolved_set`) so that /// `resolved_set` stays small even under sequential delivery. /// +/// **Reordering Invariant**: If extreme reordering (> `window` packets) causes eviction +/// from `resolved_set` above the watermark, any late duplicate arriving generates a +/// benign spurious NACK that the sender's bounded `RetxBuffer` / TTL drops with zero +/// side effects. +/// /// # Pending-set bound /// /// The pending set is also bounded to `window` entries; when it would exceed @@ -497,4 +525,84 @@ mod tests { r2.missing ); } + + #[test] + fn multisymbol_partial_receive_promoted_and_resolved() { + let mut d = LossDetector::new(5, 1024); + // Counter seen at t=0 (e.g. symbol 0 arrived, symbol 1 lost). + d.on_seen(42, 0); + + // Before grace expires (t=3 < 5): not missing. + assert!(d.report(3).missing.is_empty()); + + // At t >= grace_ms (t=10 >= 5): promoted to missing and marked resolved. + let r1 = d.report(10); + assert_eq!(r1.missing, vec![42]); + + // Duplicate arrives at t+100 (t=110). + d.on_seen(42, 110); + + // Even after another grace period (t=120 or beyond), not re-reported. + let r2 = d.report(120); + assert!( + r2.missing.is_empty(), + "counter 42 must not be re-reported after resolution; got {:?}", + r2.missing + ); + } + + #[test] + fn watermark_advances_over_resolved_prefix_under_partial_receive() { + let mut d = LossDetector::new(5, 1024); + assert_eq!(d.resolved_below, 0); + + // Counter 0: seen and delivered at t=0. + d.on_seen(0, 0); + d.on_delivered(0); + assert_eq!(d.resolved_below, 1); + assert!(d.resolved_set.is_empty()); + + // Counter 1: partial receive (symbol 0 received, symbol 1 lost; never delivered). + d.on_seen(1, 0); + + // Counter 2: delivered out of order ahead of counter 1. + d.on_seen(2, 0); + d.on_delivered(2); + // Watermark remains at 1 because counter 1 is still pending. + assert_eq!(d.resolved_below, 1); + assert_eq!(d.resolved_set.len(), 1); + assert!(d.resolved_set.contains_key(&2)); + + // At t=10 (>= grace_ms), report() promotes counter 1 to missing. + // Both 1 (promoted) and 2 (delivered) now form a contiguous resolved prefix [1, 2]. + let rep1 = d.report(10); + assert_eq!(rep1.missing, vec![1]); + + // resolved_below advances monotonically from 1 past 1 and 2 to 3, + // draining resolved_set and bounding memory. + assert_eq!(d.resolved_below, 3); + assert!( + d.resolved_set.is_empty(), + "resolved_set must be drained when watermark advances" + ); + + // Consecutive pattern: counter 3 is partial receive, counters 4 and 5 delivered. + d.on_seen(3, 10); + d.on_seen(4, 10); + d.on_delivered(4); + d.on_seen(5, 10); + d.on_delivered(5); + + assert_eq!(d.resolved_below, 3); + assert_eq!(d.resolved_set.len(), 2); + + // Grace expires for counter 3 at t=20. + let rep2 = d.report(20); + assert_eq!(rep2.missing, vec![3]); + assert_eq!(d.resolved_below, 6); + assert!( + d.resolved_set.is_empty(), + "resolved_set must be drained when watermark advances to 6" + ); + } } From 98530f0c0c170d3fd3e46dc5f19653130e2f8964 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Oct 2026 21:49:02 +0000 Subject: [PATCH 02/11] bench(wan): record automated WAN benchmark results [skip ci] --- README.md | 8 +- assets/bench-fec-recovery.svg | 38 +++++----- assets/bench-throughput-loss.svg | 58 +++++++-------- crates/yip-bench/RESULTS.md | 30 ++++---- .../wan/results/20261021T213653Z/wan.csv | 73 +++++++++++++++++++ crates/yip-bench/wan/results/history.csv | 72 ++++++++++++++++++ .../yip-bench/wan/results/readme-snippet.md | 8 +- 7 files changed, 216 insertions(+), 71 deletions(-) create mode 100644 crates/yip-bench/wan/results/20261021T213653Z/wan.csv diff --git a/README.md b/README.md index 163bf4fc..8af8eb39 100644 --- a/README.md +++ b/README.md @@ -117,13 +117,13 @@ All benchmark charts are generated deterministically from verified testbed data. -*Two 1-vCPU Debian 13 VPSes, ~60.472 ms apart, 2026-10-20. yip trades raw clean-path throughput for loss resilience — see the FEC row.* +*Two 1-vCPU Debian 13 VPSes, ~54.400 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* | | TCP 1-stream | UDP loss (200M) | effective loss @10% injected | |---|--:|--:|--:| -| **yip** | 128.9 Mbit/s | 0.00% | 0% | -| **WireGuard** | 326.6 Mbit/s | 0.07% | 12% | -| **OpenVPN** | 18.1 Mbit/s | 29.84% | 9% | +| **yip** | 198.6 Mbit/s | 0.00% | 0% | +| **WireGuard** | 419.1 Mbit/s | 1.68% | 5% | +| **OpenVPN** | 19.6 Mbit/s | 1.40% | 7% | diff --git a/assets/bench-fec-recovery.svg b/assets/bench-fec-recovery.svg index 6dbba2ec..a6417969 100644 --- a/assets/bench-fec-recovery.svg +++ b/assets/bench-fec-recovery.svg @@ -45,33 +45,33 @@ 10% Injected Network Loss (netem) Effective Ping Packet Loss % - - + + - - - - - + + + + + - - - - - + + + + + - - - - + + + + - + - - 12% effective loss + + 5% effective loss 0% loss (100% recovered) diff --git a/assets/bench-throughput-loss.svg b/assets/bench-throughput-loss.svg index 5b8fe141..3f57eb3d 100644 --- a/assets/bench-throughput-loss.svg +++ b/assets/bench-throughput-loss.svg @@ -53,42 +53,42 @@ 10% Netem Injected Packet Loss TCP Goodput (Mbit/s, log scale) - - - + + + - - - - - - - + + + + + + + - - - + + + - + - - - - - - - 52.3M - - 98× advantage (117.6 vs 1.2 Mbit/s) - - 56.4 Mbit/s + + + + + + + 44.0M + + 98× advantage (117.6 vs 1.2 Mbit/s) + + 22.1 Mbit/s 0.5 Mbit/s - - + + - - 33× higher (16.6 vs 0.5) + + 33× higher (16.6 vs 0.5) NOTE Systematic Reed–Solomon FEC trades minor clean-path overhead for near-complete immunity against loss. diff --git a/crates/yip-bench/RESULTS.md b/crates/yip-bench/RESULTS.md index eba06562..fafe5a25 100644 --- a/crates/yip-bench/RESULTS.md +++ b/crates/yip-bench/RESULTS.md @@ -740,8 +740,8 @@ Regime F delivers: ### Conditions - **Hosts:** `EC2 us-east-1 (t3.small)` ↔ `EC2 us-west-2 (t3.small)` (two 1-vCPU / ~1 GB Debian 13 VPSes, kernel 6.1.0-54-cloud-amd64) -- **Baseline path:** ~60.472 ms RTT, single Gbit links -- **Date / commit:** 2026-10-20T21:55:23Z / `7c2fe8c` +- **Baseline path:** ~54.400 ms RTT, single Gbit links +- **Date / commit:** 2026-10-21T21:37:08Z / `d484d84` - **Host tuning (applied equally, all tunnels):** `net.core.rmem_max=net.core.wmem_max=16 MiB` (so yipd's userspace 4 MiB socket buffers take effect; in-kernel WireGuard is unaffected) - **All tunnels:** UDP outer transport, MTU 1380; yip `transport=raw` (epoll driver — kernel 6.12 has no io_uring multishot recv, #25); WireGuard kernel module; OpenVPN static-key AES-256-GCM @@ -751,10 +751,10 @@ Regime F delivers: | tunnel | TCP 1-stream | TCP 4-stream | UDP goodput | UDP loss | idle RTT | RTT under load | CPU (A, 1 vCPU) | |---|--:|--:|--:|--:|--:|--:|--:| -| baseline (no tunnel) | 361.7 Mbit/s | 1019.2 Mbit/s | 200.0 Mbit/s | 0.00% | 60.593 ms | 60.561 ms | 5.9% | -| yip | 128.9 Mbit/s | 175.7 Mbit/s | 200.0 Mbit/s | 0.00% | 60.161 ms | 66.018 ms | 27.6% | -| WireGuard | 326.6 Mbit/s | 794.2 Mbit/s | 200.0 Mbit/s | 0.07% | 63.741 ms | 64.081 ms | 13.5% | -| OpenVPN | 18.1 Mbit/s | 23.4 Mbit/s | 200.0 Mbit/s | 29.84% | 62.594 ms | 62.776 ms | 6.1% | +| baseline (no tunnel) | 299.0 Mbit/s | 1148.2 Mbit/s | 200.0 Mbit/s | 0.00% | 55.167 ms | 55.106 ms | 3.8% | +| yip | 198.6 Mbit/s | 233.2 Mbit/s | 200.0 Mbit/s | 0.00% | 55.775 ms | 58.461 ms | 26.2% | +| WireGuard | 419.1 Mbit/s | 1623.5 Mbit/s | 200.0 Mbit/s | 1.68% | 55.626 ms | 55.660 ms | 12.2% | +| OpenVPN | 19.6 Mbit/s | 22.1 Mbit/s | 200.0 Mbit/s | 1.40% | 55.978 ms | 56.223 ms | 3.0% | ### Loss resilience (netem injected on the A→B outer path) @@ -763,20 +763,20 @@ Effective end-to-end loss (ping) — lower is better; yip's FEC recovers what th | injected loss | baseline (no tunnel) | yip | WireGuard | OpenVPN | |---|--:|--:|--:|--:| | 0% | 0% | 0% | 0% | 0% | -| 1% | 1% | 0% | 1% | 0% | -| 3% | 1% | 0% | 3% | 6% | -| 5% | 3% | 1% | 5% | 4% | -| 10% | 4% | 0% | 12% | 9% | +| 1% | 2% | 0% | 1% | 4% | +| 3% | 8% | 0% | 1% | 1% | +| 5% | 8% | 0% | 7% | 6% | +| 10% | 13% | 0% | 5% | 7% | TCP goodput under the same injected loss (Mbit/s): | injected loss | baseline (no tunnel) | yip | WireGuard | OpenVPN | |---|--:|--:|--:|--:| -| 0% | 384.9 | 169.0 | 258.5 | 21.7 | -| 1% | 15.3 | 110.2 | 3.5 | 2.5 | -| 3% | 1.7 | 52.3 | 1.2 | 0.9 | -| 5% | 1.3 | 12.4 | 0.7 | 0.7 | -| 10% | 0.8 | 56.4 | 0.5 | 0.5 | +| 0% | 383.4 | 192.3 | 418.9 | 18.8 | +| 1% | 3.4 | 236.8 | 3.1 | 2.3 | +| 3% | 1.7 | 44.0 | 1.2 | 1.2 | +| 5% | 1.0 | 51.1 | 0.8 | 0.9 | +| 10% | 0.7 | 22.1 | 0.5 | 0.5 | ### Reproduce ```sh diff --git a/crates/yip-bench/wan/results/20261021T213653Z/wan.csv b/crates/yip-bench/wan/results/20261021T213653Z/wan.csv new file mode 100644 index 00000000..9f01b7ff --- /dev/null +++ b/crates/yip-bench/wan/results/20261021T213653Z/wan.csv @@ -0,0 +1,73 @@ +ts,host_pair,commit,tunnel,profile,param,metric,value,conditions +2026-10-21T21:37:08Z,54.88.26.78->35.94.252.101,d484d84,baseline,tcp_throughput,streams=1,mbit_s,299.0,clean +2026-10-21T21:37:22Z,54.88.26.78->35.94.252.101,d484d84,baseline,tcp_throughput,streams=4,mbit_s,1148.2,clean +2026-10-21T21:37:33Z,54.88.26.78->35.94.252.101,d484d84,baseline,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:37:33Z,54.88.26.78->35.94.252.101,d484d84,baseline,udp_jitter,offered=200M,ms,0.003,clean +2026-10-21T21:37:33Z,54.88.26.78->35.94.252.101,d484d84,baseline,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T21:37:50Z,54.88.26.78->35.94.252.101,d484d84,baseline,latency,idle,rtt_ms,55.167,clean +2026-10-21T21:37:50Z,54.88.26.78->35.94.252.101,d484d84,baseline,latency,loaded,rtt_ms,55.106,saturated +2026-10-21T21:37:57Z,54.88.26.78->35.94.252.101,d484d84,baseline,cpu_used,tcp_saturate,pct,3.8,sender_host_A_1vcpu +2026-10-21T21:38:17Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:38:17Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_goodput,injected=0pct,mbit_s,383.4,netem_0pct +2026-10-21T21:38:38Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_effective,injected=1pct,pct,2,netem_1pct +2026-10-21T21:38:38Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_goodput,injected=1pct,mbit_s,3.4,netem_1pct +2026-10-21T21:39:00Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_effective,injected=3pct,pct,8,netem_3pct +2026-10-21T21:39:00Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_goodput,injected=3pct,mbit_s,1.7,netem_3pct +2026-10-21T21:39:22Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_effective,injected=5pct,pct,8,netem_5pct +2026-10-21T21:39:22Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_goodput,injected=5pct,mbit_s,1.0,netem_5pct +2026-10-21T21:39:43Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_effective,injected=10pct,pct,13,netem_10pct +2026-10-21T21:39:43Z,54.88.26.78->35.94.252.101,d484d84,baseline,loss_goodput,injected=10pct,mbit_s,0.7,netem_10pct +2026-10-21T21:39:57Z,54.88.26.78->35.94.252.101,d484d84,yip,tcp_throughput,streams=1,mbit_s,198.6,clean +2026-10-21T21:40:11Z,54.88.26.78->35.94.252.101,d484d84,yip,tcp_throughput,streams=4,mbit_s,233.2,clean +2026-10-21T21:40:22Z,54.88.26.78->35.94.252.101,d484d84,yip,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:40:22Z,54.88.26.78->35.94.252.101,d484d84,yip,udp_jitter,offered=200M,ms,0.013,clean +2026-10-21T21:40:22Z,54.88.26.78->35.94.252.101,d484d84,yip,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T21:40:39Z,54.88.26.78->35.94.252.101,d484d84,yip,latency,idle,rtt_ms,55.775,clean +2026-10-21T21:40:39Z,54.88.26.78->35.94.252.101,d484d84,yip,latency,loaded,rtt_ms,58.461,saturated +2026-10-21T21:40:46Z,54.88.26.78->35.94.252.101,d484d84,yip,cpu_used,tcp_saturate,pct,26.2,sender_host_A_1vcpu +2026-10-21T21:41:06Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:41:06Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_goodput,injected=0pct,mbit_s,192.3,netem_0pct +2026-10-21T21:41:28Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T21:41:28Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_goodput,injected=1pct,mbit_s,236.8,netem_1pct +2026-10-21T21:41:49Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_effective,injected=3pct,pct,0,netem_3pct +2026-10-21T21:41:49Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_goodput,injected=3pct,mbit_s,44.0,netem_3pct +2026-10-21T21:42:11Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_effective,injected=5pct,pct,0,netem_5pct +2026-10-21T21:42:11Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_goodput,injected=5pct,mbit_s,51.1,netem_5pct +2026-10-21T21:42:32Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_effective,injected=10pct,pct,0,netem_10pct +2026-10-21T21:42:32Z,54.88.26.78->35.94.252.101,d484d84,yip,loss_goodput,injected=10pct,mbit_s,22.1,netem_10pct +2026-10-21T21:42:46Z,54.88.26.78->35.94.252.101,d484d84,wireguard,tcp_throughput,streams=1,mbit_s,419.1,clean +2026-10-21T21:43:00Z,54.88.26.78->35.94.252.101,d484d84,wireguard,tcp_throughput,streams=4,mbit_s,1623.5,clean +2026-10-21T21:43:11Z,54.88.26.78->35.94.252.101,d484d84,wireguard,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:43:11Z,54.88.26.78->35.94.252.101,d484d84,wireguard,udp_jitter,offered=200M,ms,0.013,clean +2026-10-21T21:43:11Z,54.88.26.78->35.94.252.101,d484d84,wireguard,udp_loss,offered=200M,pct,1.68,clean +2026-10-21T21:43:28Z,54.88.26.78->35.94.252.101,d484d84,wireguard,latency,idle,rtt_ms,55.626,clean +2026-10-21T21:43:28Z,54.88.26.78->35.94.252.101,d484d84,wireguard,latency,loaded,rtt_ms,55.660,saturated +2026-10-21T21:43:35Z,54.88.26.78->35.94.252.101,d484d84,wireguard,cpu_used,tcp_saturate,pct,12.2,sender_host_A_1vcpu +2026-10-21T21:43:55Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:43:55Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_goodput,injected=0pct,mbit_s,418.9,netem_0pct +2026-10-21T21:44:17Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_effective,injected=1pct,pct,1,netem_1pct +2026-10-21T21:44:17Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_goodput,injected=1pct,mbit_s,3.1,netem_1pct +2026-10-21T21:44:38Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_effective,injected=3pct,pct,1,netem_3pct +2026-10-21T21:44:38Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_goodput,injected=3pct,mbit_s,1.2,netem_3pct +2026-10-21T21:45:00Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_effective,injected=5pct,pct,7,netem_5pct +2026-10-21T21:45:00Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct +2026-10-21T21:45:23Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_effective,injected=10pct,pct,5,netem_10pct +2026-10-21T21:45:23Z,54.88.26.78->35.94.252.101,d484d84,wireguard,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct +2026-10-21T21:45:37Z,54.88.26.78->35.94.252.101,d484d84,openvpn,tcp_throughput,streams=1,mbit_s,19.6,clean +2026-10-21T21:45:50Z,54.88.26.78->35.94.252.101,d484d84,openvpn,tcp_throughput,streams=4,mbit_s,22.1,clean +2026-10-21T21:46:01Z,54.88.26.78->35.94.252.101,d484d84,openvpn,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:46:01Z,54.88.26.78->35.94.252.101,d484d84,openvpn,udp_jitter,offered=200M,ms,0.321,clean +2026-10-21T21:46:01Z,54.88.26.78->35.94.252.101,d484d84,openvpn,udp_loss,offered=200M,pct,1.40,clean +2026-10-21T21:46:18Z,54.88.26.78->35.94.252.101,d484d84,openvpn,latency,idle,rtt_ms,55.978,clean +2026-10-21T21:46:18Z,54.88.26.78->35.94.252.101,d484d84,openvpn,latency,loaded,rtt_ms,56.223,saturated +2026-10-21T21:46:25Z,54.88.26.78->35.94.252.101,d484d84,openvpn,cpu_used,tcp_saturate,pct,3.0,sender_host_A_1vcpu +2026-10-21T21:46:45Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:46:45Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_goodput,injected=0pct,mbit_s,18.8,netem_0pct +2026-10-21T21:47:07Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_effective,injected=1pct,pct,4,netem_1pct +2026-10-21T21:47:07Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_goodput,injected=1pct,mbit_s,2.3,netem_1pct +2026-10-21T21:47:28Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_effective,injected=3pct,pct,1,netem_3pct +2026-10-21T21:47:28Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_goodput,injected=3pct,mbit_s,1.2,netem_3pct +2026-10-21T21:47:50Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_effective,injected=5pct,pct,6,netem_5pct +2026-10-21T21:47:50Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct +2026-10-21T21:48:12Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_effective,injected=10pct,pct,7,netem_10pct +2026-10-21T21:48:12Z,54.88.26.78->35.94.252.101,d484d84,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct diff --git a/crates/yip-bench/wan/results/history.csv b/crates/yip-bench/wan/results/history.csv index ef39b83c..6e702efc 100644 --- a/crates/yip-bench/wan/results/history.csv +++ b/crates/yip-bench/wan/results/history.csv @@ -359,3 +359,75 @@ ts,host_pair,commit,tunnel,profile,param,metric,value,conditions 2026-10-20T22:07:53Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),7c2fe8c,openvpn,loss_goodput,injected=5pct,mbit_s,0.7,netem_5pct 2026-10-20T22:08:13Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),7c2fe8c,openvpn,loss_effective,injected=10pct,pct,9,netem_10pct 2026-10-20T22:08:13Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),7c2fe8c,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct +2026-10-21T21:37:08Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,tcp_throughput,streams=1,mbit_s,299.0,clean +2026-10-21T21:37:22Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,tcp_throughput,streams=4,mbit_s,1148.2,clean +2026-10-21T21:37:33Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:37:33Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,udp_jitter,offered=200M,ms,0.003,clean +2026-10-21T21:37:33Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T21:37:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,latency,idle,rtt_ms,55.167,clean +2026-10-21T21:37:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,latency,loaded,rtt_ms,55.106,saturated +2026-10-21T21:37:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,cpu_used,tcp_saturate,pct,3.8,sender_host_A_1vcpu +2026-10-21T21:38:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:38:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_goodput,injected=0pct,mbit_s,383.4,netem_0pct +2026-10-21T21:38:38Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_effective,injected=1pct,pct,2,netem_1pct +2026-10-21T21:38:38Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_goodput,injected=1pct,mbit_s,3.4,netem_1pct +2026-10-21T21:39:00Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_effective,injected=3pct,pct,8,netem_3pct +2026-10-21T21:39:00Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_goodput,injected=3pct,mbit_s,1.7,netem_3pct +2026-10-21T21:39:22Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_effective,injected=5pct,pct,8,netem_5pct +2026-10-21T21:39:22Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_goodput,injected=5pct,mbit_s,1.0,netem_5pct +2026-10-21T21:39:43Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_effective,injected=10pct,pct,13,netem_10pct +2026-10-21T21:39:43Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,baseline,loss_goodput,injected=10pct,mbit_s,0.7,netem_10pct +2026-10-21T21:39:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,tcp_throughput,streams=1,mbit_s,198.6,clean +2026-10-21T21:40:11Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,tcp_throughput,streams=4,mbit_s,233.2,clean +2026-10-21T21:40:22Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:40:22Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,udp_jitter,offered=200M,ms,0.013,clean +2026-10-21T21:40:22Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T21:40:39Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,latency,idle,rtt_ms,55.775,clean +2026-10-21T21:40:39Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,latency,loaded,rtt_ms,58.461,saturated +2026-10-21T21:40:46Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,cpu_used,tcp_saturate,pct,26.2,sender_host_A_1vcpu +2026-10-21T21:41:06Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:41:06Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_goodput,injected=0pct,mbit_s,192.3,netem_0pct +2026-10-21T21:41:28Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T21:41:28Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_goodput,injected=1pct,mbit_s,236.8,netem_1pct +2026-10-21T21:41:49Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_effective,injected=3pct,pct,0,netem_3pct +2026-10-21T21:41:49Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_goodput,injected=3pct,mbit_s,44.0,netem_3pct +2026-10-21T21:42:11Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_effective,injected=5pct,pct,0,netem_5pct +2026-10-21T21:42:11Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_goodput,injected=5pct,mbit_s,51.1,netem_5pct +2026-10-21T21:42:32Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_effective,injected=10pct,pct,0,netem_10pct +2026-10-21T21:42:32Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,yip,loss_goodput,injected=10pct,mbit_s,22.1,netem_10pct +2026-10-21T21:42:46Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,tcp_throughput,streams=1,mbit_s,419.1,clean +2026-10-21T21:43:00Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,tcp_throughput,streams=4,mbit_s,1623.5,clean +2026-10-21T21:43:11Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:43:11Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,udp_jitter,offered=200M,ms,0.013,clean +2026-10-21T21:43:11Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,udp_loss,offered=200M,pct,1.68,clean +2026-10-21T21:43:28Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,latency,idle,rtt_ms,55.626,clean +2026-10-21T21:43:28Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,latency,loaded,rtt_ms,55.660,saturated +2026-10-21T21:43:35Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,cpu_used,tcp_saturate,pct,12.2,sender_host_A_1vcpu +2026-10-21T21:43:55Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:43:55Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_goodput,injected=0pct,mbit_s,418.9,netem_0pct +2026-10-21T21:44:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_effective,injected=1pct,pct,1,netem_1pct +2026-10-21T21:44:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_goodput,injected=1pct,mbit_s,3.1,netem_1pct +2026-10-21T21:44:38Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_effective,injected=3pct,pct,1,netem_3pct +2026-10-21T21:44:38Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_goodput,injected=3pct,mbit_s,1.2,netem_3pct +2026-10-21T21:45:00Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_effective,injected=5pct,pct,7,netem_5pct +2026-10-21T21:45:00Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct +2026-10-21T21:45:23Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_effective,injected=10pct,pct,5,netem_10pct +2026-10-21T21:45:23Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,wireguard,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct +2026-10-21T21:45:37Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,tcp_throughput,streams=1,mbit_s,19.6,clean +2026-10-21T21:45:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,tcp_throughput,streams=4,mbit_s,22.1,clean +2026-10-21T21:46:01Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:46:01Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,udp_jitter,offered=200M,ms,0.321,clean +2026-10-21T21:46:01Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,udp_loss,offered=200M,pct,1.40,clean +2026-10-21T21:46:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,latency,idle,rtt_ms,55.978,clean +2026-10-21T21:46:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,latency,loaded,rtt_ms,56.223,saturated +2026-10-21T21:46:25Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,cpu_used,tcp_saturate,pct,3.0,sender_host_A_1vcpu +2026-10-21T21:46:45Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:46:45Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_goodput,injected=0pct,mbit_s,18.8,netem_0pct +2026-10-21T21:47:07Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_effective,injected=1pct,pct,4,netem_1pct +2026-10-21T21:47:07Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_goodput,injected=1pct,mbit_s,2.3,netem_1pct +2026-10-21T21:47:28Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_effective,injected=3pct,pct,1,netem_3pct +2026-10-21T21:47:28Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_goodput,injected=3pct,mbit_s,1.2,netem_3pct +2026-10-21T21:47:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_effective,injected=5pct,pct,6,netem_5pct +2026-10-21T21:47:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct +2026-10-21T21:48:12Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_effective,injected=10pct,pct,7,netem_10pct +2026-10-21T21:48:12Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct diff --git a/crates/yip-bench/wan/results/readme-snippet.md b/crates/yip-bench/wan/results/readme-snippet.md index 7871ad47..4d205400 100644 --- a/crates/yip-bench/wan/results/readme-snippet.md +++ b/crates/yip-bench/wan/results/readme-snippet.md @@ -1,8 +1,8 @@ -*Two 1-vCPU Debian 13 VPSes, ~60.472 ms apart, 2026-10-20. yip trades raw clean-path throughput for loss resilience — see the FEC row.* +*Two 1-vCPU Debian 13 VPSes, ~54.400 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* | | TCP 1-stream | UDP loss (200M) | effective loss @10% injected | |---|--:|--:|--:| -| **yip** | 128.9 Mbit/s | 0.00% | 0% | -| **WireGuard** | 326.6 Mbit/s | 0.07% | 12% | -| **OpenVPN** | 18.1 Mbit/s | 29.84% | 9% | +| **yip** | 198.6 Mbit/s | 0.00% | 0% | +| **WireGuard** | 419.1 Mbit/s | 1.68% | 5% | +| **OpenVPN** | 19.6 Mbit/s | 1.40% | 7% | From ddc31088e081e10c02a489fd803de383c6189c2c Mon Sep 17 00:00:00 2001 From: Zoa Hickenlooper Date: Wed, 21 Oct 2026 17:49:50 -0400 Subject: [PATCH 03/11] fix(deps): update tokio to 1.54.1 to resolve crates.io yank advisory --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8433c9b..d78352d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1923,9 +1923,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.54.0" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c168a474b95bfecfe908a65d4bdffc523144b649a74c09c286afb35d53e0bc" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -2133,7 +2133,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] From 620f5506773f44799e7767a0197e77b0742fe5d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Oct 2026 22:06:29 +0000 Subject: [PATCH 04/11] bench(wan): record automated WAN benchmark results [skip ci] --- README.md | 8 +- assets/bench-fec-recovery.svg | 38 +++++----- assets/bench-throughput-loss.svg | 66 ++++++++--------- crates/yip-bench/RESULTS.md | 30 ++++---- .../wan/results/20261021T215456Z/wan.csv | 73 +++++++++++++++++++ crates/yip-bench/wan/results/history.csv | 72 ++++++++++++++++++ .../yip-bench/wan/results/readme-snippet.md | 8 +- 7 files changed, 220 insertions(+), 75 deletions(-) create mode 100644 crates/yip-bench/wan/results/20261021T215456Z/wan.csv diff --git a/README.md b/README.md index 8af8eb39..df67aae7 100644 --- a/README.md +++ b/README.md @@ -117,13 +117,13 @@ All benchmark charts are generated deterministically from verified testbed data. -*Two 1-vCPU Debian 13 VPSes, ~54.400 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* +*Two 1-vCPU Debian 13 VPSes, ~55.073 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* | | TCP 1-stream | UDP loss (200M) | effective loss @10% injected | |---|--:|--:|--:| -| **yip** | 198.6 Mbit/s | 0.00% | 0% | -| **WireGuard** | 419.1 Mbit/s | 1.68% | 5% | -| **OpenVPN** | 19.6 Mbit/s | 1.40% | 7% | +| **yip** | 248.8 Mbit/s | 0.00% | 3% | +| **WireGuard** | 379.5 Mbit/s | 0.01% | 9% | +| **OpenVPN** | 19.6 Mbit/s | 0.39% | 7% | diff --git a/assets/bench-fec-recovery.svg b/assets/bench-fec-recovery.svg index a6417969..6e1aa7ac 100644 --- a/assets/bench-fec-recovery.svg +++ b/assets/bench-fec-recovery.svg @@ -45,35 +45,35 @@ 10% Injected Network Loss (netem) Effective Ping Packet Loss % - - + + - - - - + + + + - - - + + + - + - - - - + + + + - - - 5% effective loss - - 0% loss (100% recovered) + + + 9% effective loss + + 0% loss (100% recovered) Cauchy RS GF(256): 100% recovery before kernel TCP timeout diff --git a/assets/bench-throughput-loss.svg b/assets/bench-throughput-loss.svg index 3f57eb3d..7f1e0521 100644 --- a/assets/bench-throughput-loss.svg +++ b/assets/bench-throughput-loss.svg @@ -53,42 +53,42 @@ 10% Netem Injected Packet Loss TCP Goodput (Mbit/s, log scale) - - - - - - - - - - + + + + + + + + + + - - - - + + + + - - - - - - - - 44.0M - - 98× advantage (117.6 vs 1.2 Mbit/s) - - 22.1 Mbit/s - - 0.5 Mbit/s - - - - - 33× higher (16.6 vs 0.5) + + + + + + + + 126.1M + + 98× advantage (117.6 vs 1.2 Mbit/s) + + 6.4 Mbit/s + + 0.6 Mbit/s + + + + + 33× higher (16.6 vs 0.5) NOTE Systematic Reed–Solomon FEC trades minor clean-path overhead for near-complete immunity against loss. diff --git a/crates/yip-bench/RESULTS.md b/crates/yip-bench/RESULTS.md index fafe5a25..db6bc8e0 100644 --- a/crates/yip-bench/RESULTS.md +++ b/crates/yip-bench/RESULTS.md @@ -740,8 +740,8 @@ Regime F delivers: ### Conditions - **Hosts:** `EC2 us-east-1 (t3.small)` ↔ `EC2 us-west-2 (t3.small)` (two 1-vCPU / ~1 GB Debian 13 VPSes, kernel 6.1.0-54-cloud-amd64) -- **Baseline path:** ~54.400 ms RTT, single Gbit links -- **Date / commit:** 2026-10-21T21:37:08Z / `d484d84` +- **Baseline path:** ~55.073 ms RTT, single Gbit links +- **Date / commit:** 2026-10-21T21:55:10Z / `ddc3108` - **Host tuning (applied equally, all tunnels):** `net.core.rmem_max=net.core.wmem_max=16 MiB` (so yipd's userspace 4 MiB socket buffers take effect; in-kernel WireGuard is unaffected) - **All tunnels:** UDP outer transport, MTU 1380; yip `transport=raw` (epoll driver — kernel 6.12 has no io_uring multishot recv, #25); WireGuard kernel module; OpenVPN static-key AES-256-GCM @@ -751,10 +751,10 @@ Regime F delivers: | tunnel | TCP 1-stream | TCP 4-stream | UDP goodput | UDP loss | idle RTT | RTT under load | CPU (A, 1 vCPU) | |---|--:|--:|--:|--:|--:|--:|--:| -| baseline (no tunnel) | 299.0 Mbit/s | 1148.2 Mbit/s | 200.0 Mbit/s | 0.00% | 55.167 ms | 55.106 ms | 3.8% | -| yip | 198.6 Mbit/s | 233.2 Mbit/s | 200.0 Mbit/s | 0.00% | 55.775 ms | 58.461 ms | 26.2% | -| WireGuard | 419.1 Mbit/s | 1623.5 Mbit/s | 200.0 Mbit/s | 1.68% | 55.626 ms | 55.660 ms | 12.2% | -| OpenVPN | 19.6 Mbit/s | 22.1 Mbit/s | 200.0 Mbit/s | 1.40% | 55.978 ms | 56.223 ms | 3.0% | +| baseline (no tunnel) | 291.7 Mbit/s | 1077.8 Mbit/s | 200.0 Mbit/s | 0.00% | 55.075 ms | 55.036 ms | 8.5% | +| yip | 248.8 Mbit/s | 256.3 Mbit/s | 200.0 Mbit/s | 0.00% | 57.580 ms | 61.091 ms | 8.8% | +| WireGuard | 379.5 Mbit/s | 455.7 Mbit/s | 200.0 Mbit/s | 0.01% | 54.876 ms | 57.976 ms | 22.4% | +| OpenVPN | 19.6 Mbit/s | 54.9 Mbit/s | 200.0 Mbit/s | 0.39% | 55.283 ms | 55.500 ms | 12.9% | ### Loss resilience (netem injected on the A→B outer path) @@ -763,20 +763,20 @@ Effective end-to-end loss (ping) — lower is better; yip's FEC recovers what th | injected loss | baseline (no tunnel) | yip | WireGuard | OpenVPN | |---|--:|--:|--:|--:| | 0% | 0% | 0% | 0% | 0% | -| 1% | 2% | 0% | 1% | 4% | -| 3% | 8% | 0% | 1% | 1% | -| 5% | 8% | 0% | 7% | 6% | -| 10% | 13% | 0% | 5% | 7% | +| 1% | 2% | 0% | 1% | 1% | +| 3% | 4% | 0% | 4% | 0% | +| 5% | 5% | 0% | 4% | 8% | +| 10% | 10% | 3% | 9% | 7% | TCP goodput under the same injected loss (Mbit/s): | injected loss | baseline (no tunnel) | yip | WireGuard | OpenVPN | |---|--:|--:|--:|--:| -| 0% | 383.4 | 192.3 | 418.9 | 18.8 | -| 1% | 3.4 | 236.8 | 3.1 | 2.3 | -| 3% | 1.7 | 44.0 | 1.2 | 1.2 | -| 5% | 1.0 | 51.1 | 0.8 | 0.9 | -| 10% | 0.7 | 22.1 | 0.5 | 0.5 | +| 0% | 426.6 | 167.9 | 361.7 | 24.3 | +| 1% | 36.4 | 61.1 | 2.7 | 2.9 | +| 3% | 1.6 | 126.1 | 1.1 | 1.4 | +| 5% | 1.4 | 53.5 | 0.8 | 0.9 | +| 10% | 0.6 | 6.4 | 0.6 | 0.5 | ### Reproduce ```sh diff --git a/crates/yip-bench/wan/results/20261021T215456Z/wan.csv b/crates/yip-bench/wan/results/20261021T215456Z/wan.csv new file mode 100644 index 00000000..70c40cfe --- /dev/null +++ b/crates/yip-bench/wan/results/20261021T215456Z/wan.csv @@ -0,0 +1,73 @@ +ts,host_pair,commit,tunnel,profile,param,metric,value,conditions +2026-10-21T21:55:10Z,34.227.57.24->35.167.199.119,ddc3108,baseline,tcp_throughput,streams=1,mbit_s,291.7,clean +2026-10-21T21:55:23Z,34.227.57.24->35.167.199.119,ddc3108,baseline,tcp_throughput,streams=4,mbit_s,1077.8,clean +2026-10-21T21:55:34Z,34.227.57.24->35.167.199.119,ddc3108,baseline,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:55:34Z,34.227.57.24->35.167.199.119,ddc3108,baseline,udp_jitter,offered=200M,ms,0.004,clean +2026-10-21T21:55:34Z,34.227.57.24->35.167.199.119,ddc3108,baseline,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T21:55:50Z,34.227.57.24->35.167.199.119,ddc3108,baseline,latency,idle,rtt_ms,55.075,clean +2026-10-21T21:55:50Z,34.227.57.24->35.167.199.119,ddc3108,baseline,latency,loaded,rtt_ms,55.036,saturated +2026-10-21T21:55:57Z,34.227.57.24->35.167.199.119,ddc3108,baseline,cpu_used,tcp_saturate,pct,8.5,sender_host_A_1vcpu +2026-10-21T21:56:16Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:56:16Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_goodput,injected=0pct,mbit_s,426.6,netem_0pct +2026-10-21T21:56:36Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_effective,injected=1pct,pct,2,netem_1pct +2026-10-21T21:56:36Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_goodput,injected=1pct,mbit_s,36.4,netem_1pct +2026-10-21T21:56:56Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_effective,injected=3pct,pct,4,netem_3pct +2026-10-21T21:56:56Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_goodput,injected=3pct,mbit_s,1.6,netem_3pct +2026-10-21T21:57:17Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_effective,injected=5pct,pct,5,netem_5pct +2026-10-21T21:57:17Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_goodput,injected=5pct,mbit_s,1.4,netem_5pct +2026-10-21T21:57:37Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_effective,injected=10pct,pct,10,netem_10pct +2026-10-21T21:57:37Z,34.227.57.24->35.167.199.119,ddc3108,baseline,loss_goodput,injected=10pct,mbit_s,0.6,netem_10pct +2026-10-21T21:57:50Z,34.227.57.24->35.167.199.119,ddc3108,yip,tcp_throughput,streams=1,mbit_s,248.8,clean +2026-10-21T21:58:04Z,34.227.57.24->35.167.199.119,ddc3108,yip,tcp_throughput,streams=4,mbit_s,256.3,clean +2026-10-21T21:58:15Z,34.227.57.24->35.167.199.119,ddc3108,yip,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:58:15Z,34.227.57.24->35.167.199.119,ddc3108,yip,udp_jitter,offered=200M,ms,0.012,clean +2026-10-21T21:58:15Z,34.227.57.24->35.167.199.119,ddc3108,yip,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T21:58:31Z,34.227.57.24->35.167.199.119,ddc3108,yip,latency,idle,rtt_ms,57.580,clean +2026-10-21T21:58:31Z,34.227.57.24->35.167.199.119,ddc3108,yip,latency,loaded,rtt_ms,61.091,saturated +2026-10-21T21:58:38Z,34.227.57.24->35.167.199.119,ddc3108,yip,cpu_used,tcp_saturate,pct,8.8,sender_host_A_1vcpu +2026-10-21T21:58:56Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:58:56Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_goodput,injected=0pct,mbit_s,167.9,netem_0pct +2026-10-21T21:59:16Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T21:59:16Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_goodput,injected=1pct,mbit_s,61.1,netem_1pct +2026-10-21T21:59:36Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_effective,injected=3pct,pct,0,netem_3pct +2026-10-21T21:59:36Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_goodput,injected=3pct,mbit_s,126.1,netem_3pct +2026-10-21T21:59:56Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_effective,injected=5pct,pct,0,netem_5pct +2026-10-21T21:59:56Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_goodput,injected=5pct,mbit_s,53.5,netem_5pct +2026-10-21T22:00:17Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_effective,injected=10pct,pct,3,netem_10pct +2026-10-21T22:00:17Z,34.227.57.24->35.167.199.119,ddc3108,yip,loss_goodput,injected=10pct,mbit_s,6.4,netem_10pct +2026-10-21T22:00:30Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,tcp_throughput,streams=1,mbit_s,379.5,clean +2026-10-21T22:00:43Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,tcp_throughput,streams=4,mbit_s,455.7,clean +2026-10-21T22:00:54Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T22:00:54Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,udp_jitter,offered=200M,ms,0.055,clean +2026-10-21T22:00:54Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,udp_loss,offered=200M,pct,0.01,clean +2026-10-21T22:01:10Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,latency,idle,rtt_ms,54.876,clean +2026-10-21T22:01:10Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,latency,loaded,rtt_ms,57.976,saturated +2026-10-21T22:01:17Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,cpu_used,tcp_saturate,pct,22.4,sender_host_A_1vcpu +2026-10-21T22:01:36Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T22:01:36Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_goodput,injected=0pct,mbit_s,361.7,netem_0pct +2026-10-21T22:01:56Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_effective,injected=1pct,pct,1,netem_1pct +2026-10-21T22:01:56Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_goodput,injected=1pct,mbit_s,2.7,netem_1pct +2026-10-21T22:02:17Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_effective,injected=3pct,pct,4,netem_3pct +2026-10-21T22:02:17Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_goodput,injected=3pct,mbit_s,1.1,netem_3pct +2026-10-21T22:02:37Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_effective,injected=5pct,pct,4,netem_5pct +2026-10-21T22:02:37Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct +2026-10-21T22:02:57Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_effective,injected=10pct,pct,9,netem_10pct +2026-10-21T22:02:57Z,34.227.57.24->35.167.199.119,ddc3108,wireguard,loss_goodput,injected=10pct,mbit_s,0.6,netem_10pct +2026-10-21T22:03:10Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,tcp_throughput,streams=1,mbit_s,19.6,clean +2026-10-21T22:03:23Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,tcp_throughput,streams=4,mbit_s,54.9,clean +2026-10-21T22:03:34Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T22:03:34Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,udp_jitter,offered=200M,ms,0.028,clean +2026-10-21T22:03:34Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,udp_loss,offered=200M,pct,0.39,clean +2026-10-21T22:03:50Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,latency,idle,rtt_ms,55.283,clean +2026-10-21T22:03:50Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,latency,loaded,rtt_ms,55.500,saturated +2026-10-21T22:03:57Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,cpu_used,tcp_saturate,pct,12.9,sender_host_A_1vcpu +2026-10-21T22:04:16Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T22:04:16Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_goodput,injected=0pct,mbit_s,24.3,netem_0pct +2026-10-21T22:04:36Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_effective,injected=1pct,pct,1,netem_1pct +2026-10-21T22:04:36Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_goodput,injected=1pct,mbit_s,2.9,netem_1pct +2026-10-21T22:04:57Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_effective,injected=3pct,pct,0,netem_3pct +2026-10-21T22:04:57Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_goodput,injected=3pct,mbit_s,1.4,netem_3pct +2026-10-21T22:05:18Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_effective,injected=5pct,pct,8,netem_5pct +2026-10-21T22:05:18Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct +2026-10-21T22:05:39Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_effective,injected=10pct,pct,7,netem_10pct +2026-10-21T22:05:39Z,34.227.57.24->35.167.199.119,ddc3108,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct diff --git a/crates/yip-bench/wan/results/history.csv b/crates/yip-bench/wan/results/history.csv index 6e702efc..278df6f3 100644 --- a/crates/yip-bench/wan/results/history.csv +++ b/crates/yip-bench/wan/results/history.csv @@ -431,3 +431,75 @@ ts,host_pair,commit,tunnel,profile,param,metric,value,conditions 2026-10-21T21:47:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct 2026-10-21T21:48:12Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_effective,injected=10pct,pct,7,netem_10pct 2026-10-21T21:48:12Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),d484d84,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct +2026-10-21T21:55:10Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,tcp_throughput,streams=1,mbit_s,291.7,clean +2026-10-21T21:55:23Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,tcp_throughput,streams=4,mbit_s,1077.8,clean +2026-10-21T21:55:34Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:55:34Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,udp_jitter,offered=200M,ms,0.004,clean +2026-10-21T21:55:34Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T21:55:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,latency,idle,rtt_ms,55.075,clean +2026-10-21T21:55:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,latency,loaded,rtt_ms,55.036,saturated +2026-10-21T21:55:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,cpu_used,tcp_saturate,pct,8.5,sender_host_A_1vcpu +2026-10-21T21:56:16Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:56:16Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_goodput,injected=0pct,mbit_s,426.6,netem_0pct +2026-10-21T21:56:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_effective,injected=1pct,pct,2,netem_1pct +2026-10-21T21:56:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_goodput,injected=1pct,mbit_s,36.4,netem_1pct +2026-10-21T21:56:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_effective,injected=3pct,pct,4,netem_3pct +2026-10-21T21:56:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_goodput,injected=3pct,mbit_s,1.6,netem_3pct +2026-10-21T21:57:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_effective,injected=5pct,pct,5,netem_5pct +2026-10-21T21:57:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_goodput,injected=5pct,mbit_s,1.4,netem_5pct +2026-10-21T21:57:37Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_effective,injected=10pct,pct,10,netem_10pct +2026-10-21T21:57:37Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,baseline,loss_goodput,injected=10pct,mbit_s,0.6,netem_10pct +2026-10-21T21:57:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,tcp_throughput,streams=1,mbit_s,248.8,clean +2026-10-21T21:58:04Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,tcp_throughput,streams=4,mbit_s,256.3,clean +2026-10-21T21:58:15Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T21:58:15Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,udp_jitter,offered=200M,ms,0.012,clean +2026-10-21T21:58:15Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T21:58:31Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,latency,idle,rtt_ms,57.580,clean +2026-10-21T21:58:31Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,latency,loaded,rtt_ms,61.091,saturated +2026-10-21T21:58:38Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,cpu_used,tcp_saturate,pct,8.8,sender_host_A_1vcpu +2026-10-21T21:58:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T21:58:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_goodput,injected=0pct,mbit_s,167.9,netem_0pct +2026-10-21T21:59:16Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T21:59:16Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_goodput,injected=1pct,mbit_s,61.1,netem_1pct +2026-10-21T21:59:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_effective,injected=3pct,pct,0,netem_3pct +2026-10-21T21:59:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_goodput,injected=3pct,mbit_s,126.1,netem_3pct +2026-10-21T21:59:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_effective,injected=5pct,pct,0,netem_5pct +2026-10-21T21:59:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_goodput,injected=5pct,mbit_s,53.5,netem_5pct +2026-10-21T22:00:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_effective,injected=10pct,pct,3,netem_10pct +2026-10-21T22:00:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,yip,loss_goodput,injected=10pct,mbit_s,6.4,netem_10pct +2026-10-21T22:00:30Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,tcp_throughput,streams=1,mbit_s,379.5,clean +2026-10-21T22:00:43Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,tcp_throughput,streams=4,mbit_s,455.7,clean +2026-10-21T22:00:54Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T22:00:54Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,udp_jitter,offered=200M,ms,0.055,clean +2026-10-21T22:00:54Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,udp_loss,offered=200M,pct,0.01,clean +2026-10-21T22:01:10Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,latency,idle,rtt_ms,54.876,clean +2026-10-21T22:01:10Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,latency,loaded,rtt_ms,57.976,saturated +2026-10-21T22:01:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,cpu_used,tcp_saturate,pct,22.4,sender_host_A_1vcpu +2026-10-21T22:01:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T22:01:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_goodput,injected=0pct,mbit_s,361.7,netem_0pct +2026-10-21T22:01:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_effective,injected=1pct,pct,1,netem_1pct +2026-10-21T22:01:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_goodput,injected=1pct,mbit_s,2.7,netem_1pct +2026-10-21T22:02:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_effective,injected=3pct,pct,4,netem_3pct +2026-10-21T22:02:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_goodput,injected=3pct,mbit_s,1.1,netem_3pct +2026-10-21T22:02:37Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_effective,injected=5pct,pct,4,netem_5pct +2026-10-21T22:02:37Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct +2026-10-21T22:02:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_effective,injected=10pct,pct,9,netem_10pct +2026-10-21T22:02:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,wireguard,loss_goodput,injected=10pct,mbit_s,0.6,netem_10pct +2026-10-21T22:03:10Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,tcp_throughput,streams=1,mbit_s,19.6,clean +2026-10-21T22:03:23Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,tcp_throughput,streams=4,mbit_s,54.9,clean +2026-10-21T22:03:34Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T22:03:34Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,udp_jitter,offered=200M,ms,0.028,clean +2026-10-21T22:03:34Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,udp_loss,offered=200M,pct,0.39,clean +2026-10-21T22:03:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,latency,idle,rtt_ms,55.283,clean +2026-10-21T22:03:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,latency,loaded,rtt_ms,55.500,saturated +2026-10-21T22:03:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,cpu_used,tcp_saturate,pct,12.9,sender_host_A_1vcpu +2026-10-21T22:04:16Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T22:04:16Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_goodput,injected=0pct,mbit_s,24.3,netem_0pct +2026-10-21T22:04:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_effective,injected=1pct,pct,1,netem_1pct +2026-10-21T22:04:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_goodput,injected=1pct,mbit_s,2.9,netem_1pct +2026-10-21T22:04:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_effective,injected=3pct,pct,0,netem_3pct +2026-10-21T22:04:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_goodput,injected=3pct,mbit_s,1.4,netem_3pct +2026-10-21T22:05:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_effective,injected=5pct,pct,8,netem_5pct +2026-10-21T22:05:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct +2026-10-21T22:05:39Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_effective,injected=10pct,pct,7,netem_10pct +2026-10-21T22:05:39Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct diff --git a/crates/yip-bench/wan/results/readme-snippet.md b/crates/yip-bench/wan/results/readme-snippet.md index 4d205400..373f79ba 100644 --- a/crates/yip-bench/wan/results/readme-snippet.md +++ b/crates/yip-bench/wan/results/readme-snippet.md @@ -1,8 +1,8 @@ -*Two 1-vCPU Debian 13 VPSes, ~54.400 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* +*Two 1-vCPU Debian 13 VPSes, ~55.073 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* | | TCP 1-stream | UDP loss (200M) | effective loss @10% injected | |---|--:|--:|--:| -| **yip** | 198.6 Mbit/s | 0.00% | 0% | -| **WireGuard** | 419.1 Mbit/s | 1.68% | 5% | -| **OpenVPN** | 19.6 Mbit/s | 1.40% | 7% | +| **yip** | 248.8 Mbit/s | 0.00% | 3% | +| **WireGuard** | 379.5 Mbit/s | 0.01% | 9% | +| **OpenVPN** | 19.6 Mbit/s | 0.39% | 7% | From efc5a5d8d04b559ab0bea91096ee0f4caf6c6f30 Mon Sep 17 00:00:00 2001 From: Zoa Hickenlooper Date: Wed, 21 Oct 2026 19:02:29 -0400 Subject: [PATCH 05/11] bench(contenders): 8-contender harness, dual TCP/UDP-under-loss evaluation, and turnkey setup tooling (#11) --- CHANGELOG.md | 4 +- ROADMAP.md | 2 +- crates/yip-bench/README.md | 75 +++- crates/yip-bench/setup-contenders.sh | 134 ++++++ crates/yip-bench/tests/run-iperf-compare.sh | 450 +++++++++++++++++--- 5 files changed, 580 insertions(+), 85 deletions(-) create mode 100755 crates/yip-bench/setup-contenders.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 031a8c20..82f7afb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,8 +27,8 @@ until 0.1.0. Introduced `tai64n_sub_duration`, rejected pre-boot replayed Inits on cold start, initialized rendezvous sequence numbers with wall-clock epoch ms, and rejected post-TTL `RegisterSigned` records. ### Added -- **Data-plane windowed loss calculation & detector robustness (`bin/yipd`, `crates/yip-transport`, `crates/yip-bench`, #11).** - Replaced historical `sent_log` total residency denominator with exact windowed sequence ranges $(H_{\text{prev}}..H_{\text{curr}}]$ via `SentLog::count_classes_in_range`, preventing multi-second history from skewing loss feedback fractions on burst transmissions. Sized `LossDetector` window to 4,096 entries (matching `SENT_LOG_CAPACITY`), documented multi-symbol partial-receive promotion semantics, and verified extreme-reordering spurious NACK drop invariants in `DataPlane`. Parameterized benchmark runners (`run-compare.sh`, `run-iperf-compare.sh`, `run-wan-bench.sh`) for configurable `PING_COUNT` (supporting 1,000 pings at 5ms intervals with fractional percentage parsing to tighten netem variance to $\pm 0.1\%$), and documented comparative contender architecture (WireGuard, OpenVPN, n2n, ZeroTier, AmneziaWG). +- **Data-plane windowed loss calculation, 8-contender harness & UDP-under-loss evaluation (`bin/yipd`, `crates/yip-transport`, `crates/yip-bench`, #11).** + Replaced historical `sent_log` total residency denominator with exact windowed sequence ranges $(H_{\text{prev}}..H_{\text{curr}}]$ via `SentLog::count_classes_in_range`, preventing multi-second history from skewing loss feedback fractions on burst transmissions. Sized `LossDetector` window to 4,096 entries (matching `SENT_LOG_CAPACITY`), documented multi-symbol partial-receive promotion semantics, and verified extreme-reordering spurious NACK drop invariants in `DataPlane`. Expanded comparative benchmark suite (`crates/yip-bench/tests/run-iperf-compare.sh`, `crates/yip-bench/setup-contenders.sh`) to evaluate 8 contenders (`yip`, `wireguard`, `amneziawg`, `openvpn`, `n2n`, `zerotier`, `tailscale`, `netbird`) across dual TCP goodput and UDP-under-loss (`iperf3 -u`) sweeps with graceful netns auto-detection, local air-gapped controller isolation, and a turnkey contender setup script. Parameterized benchmark runners (`run-compare.sh`, `run-iperf-compare.sh`, `run-wan-bench.sh`) for configurable `PING_COUNT` (supporting 1,000 pings at 5ms intervals with fractional percentage parsing to tighten netem variance to $\pm 0.1\%$). - **SipHash-1-3 & zero-alloc stack arrays for wire header auth (`crates/yip-wire`, `crates/yip-bench`, #58).** Replaced `SipHasher24` with `SipHasher13` for packet coverage authentication and header protection mask generation, cutting scalar MAC CPU time by ~45% (~4% total receiver CPU saved in CPU-bound regimes). Replaced dynamic `Vec` concatenation in `deframe` with streaming multi-slice hashing (`hasher.write(header); hasher.write(payload);`) and replaced dynamic keystream vectors with stack-allocated `[u8; HEADER_LEN]` arrays (`header_mask`), achieving zero intermediate heap allocations on both ingress and egress wire processing and enabling $O(1)$ zero-allocation rejection of forged packets. - **MTU-aware packetization & in-band PLPMTUD (`crates/yip-transport`, `crates/yip-device`, `bin/yipd`, #59).** diff --git a/ROADMAP.md b/ROADMAP.md index 66a971d3..9b075254 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -52,7 +52,7 @@ maintainer. - **SipHash-1-3 & zero-alloc wire header auth (#58)** — streaming multi-slice SipHash-1-3 coverage authentication and stack-allocated keystream generation (`header_mask`), slashing scalar MAC CPU by ~45% and eliminating intermediate heap allocations on data-plane framing. -- **Data-plane windowed loss & detector robustness (#11)** — exact per-class windowed loss fractions ($L_{\text{class}} = M_{\text{class}} / S_{\text{class}}$ in $(H_{\text{prev}}..H_{\text{curr}}]$), 4096-entry synchronized loss detection window matching sent-log capacity, bounded spurious NACK drop invariants, and configurable 1000-ping variance tightening. +- **Data-plane windowed loss, 8-contender harness & bench polish (#11)** — exact per-class windowed loss fractions ($L_{\text{class}} = M_{\text{class}} / S_{\text{class}}$ in $(H_{\text{prev}}..H_{\text{curr}}]$), 4096-entry synchronized loss detection window matching sent-log capacity, bounded spurious NACK drop invariants, 8-contender comparison matrix and netns runners (yip, WireGuard, AmneziaWG, OpenVPN, n2n, ZeroTier, Tailscale, NetBird), dual TCP & UDP-under-loss evaluation, and configurable 1000-ping variance tightening. ## Now diff --git a/crates/yip-bench/README.md b/crates/yip-bench/README.md index e7a8dd72..f6834a7c 100644 --- a/crates/yip-bench/README.md +++ b/crates/yip-bench/README.md @@ -289,14 +289,16 @@ cheap on a clean fast path where every tunnel is fast enough. The benchmark harness evaluates and compares yip against contemporary VPN, mesh, and FEC network transports. Below is an architectural breakdown of each contender, how they operate in the benchmark environment, and how their design maps to yip: -| Contender | Execution Context | Encapsulation & Protocol | Cryptography | Loss Recovery (FEC) | Role & Benchmark Status | -|-----------|-------------------|--------------------------|--------------|---------------------|--------------------------| -| **yip** | Userspace (`yipd`) | UDP tunnel (TUN, MTU 1184), custom framed wire format | ChaCha20-Poly1305 + SipHash-2-4 | Proactive Reed–Solomon over GF(256) + Reactive ARQ | Primary project under test | -| **WireGuard** | In-kernel module (`wireguard`) | UDP (L3 TUN, MTU 1420), Noise IKpsk2 | ChaCha20-Poly1305, Curve25519, BLAKE2s | None | In-tree kernel baseline | -| **OpenVPN** | Userspace (`openvpn`) | UDP (L3 TUN), TLS control + data channel | AES-256-GCM (peer-fingerprint EC certs) | None | Full-featured legacy / enterprise tunnel | -| **n2n** | Userspace (`edge` + `supernode`) | UDP (L2 TAP), centralized discovery + P2P | Twofish / AES-CBC | None | P2P edge mesh with supernode directory | -| **ZeroTier** | System daemon (`zerotier-one`) | Encrypted UDP (L2 TAP virtual switch) | Curve25519, Salsa20/12, Poly1305 | None | Global planetary SDN mesh (architectural contender) | -| **AmneziaWG** | DKMS kernel / userspace Go | UDP (L3 TUN), obfuscated WireGuard headers | ChaCha20-Poly1305 (obfuscated) | None | DPI-resistant WireGuard fork (architectural contender) | +| Contender | Execution Context | Encapsulation & Protocol | Cryptography | Loss Recovery (FEC) | NAT Traversal / Control Plane | +|-----------|-------------------|--------------------------|--------------|---------------------|-------------------------------| +| **yip** | Userspace (`yipd`) | UDP tunnel (TUN, MTU 1184), custom framed wire format | ChaCha20-Poly1305 + SipHash-1-3 | Proactive Reed–Solomon over GF(256) + Reactive ARQ | Rendezvous + gossip discovery + blind relay | +| **WireGuard** | In-kernel module (`wireguard`) | UDP (L3 TUN, MTU 1420), Noise IKpsk2 | ChaCha20-Poly1305, Curve25519, BLAKE2s | None | Static cryptokey routing | +| **AmneziaWG** | Userspace / DKMS (`amneziawg-go`, `awg`) | UDP (L3 TUN, MTU 1420), obfuscated WireGuard headers | ChaCha20-Poly1305 (obfuscated) | None | Static cryptokey routing | +| **OpenVPN** | Userspace (`openvpn`) | UDP (L3 TUN), TLS control + data channel | AES-256-GCM (peer-fingerprint EC certs) | None | Client-server | +| **n2n** | Userspace (`edge` + `supernode`) | UDP (L2 TAP), centralized discovery + P2P | Twofish / AES-CBC | None | Supernode directory + P2P hole punching | +| **ZeroTier** | System daemon (`zerotier-one`) | Encrypted UDP (L2 TAP virtual switch) | Curve25519, Salsa20/12, Poly1305 | None | Local netns controller / planetary roots | +| **Tailscale** | System daemon (`tailscaled`) | WireGuard-in-userspace (`wireguard-go`) | ChaCha20-Poly1305, Curve25519 | None | Local Headscale coordinator / DERP relays | +| **NetBird** | System daemon (`netbird`) | WireGuard-in-userspace (`wireguard-go`) | ChaCha20-Poly1305, Curve25519 | None | WebRTC ICE / STUN / TURN + Management API | ### Detailed Architectural Analysis @@ -321,18 +323,59 @@ The benchmark harness evaluates and compares yip against contemporary VPN, mesh, #### 4. ZeroTier (Planetary Virtual SDN) - **Architecture:** ZeroTier implements a network virtualization engine (VL2 / VL1) presenting a virtual Ethernet switch (L2 TAP) across physical networks. Nodes run a persistent `zerotier-one` system daemon that communicates with planetary root servers ("planets") and custom federated relays ("moons"). -- **Why External & Not Bundled in Standalone Netns Benchmarks:** - - *System Daemon Requirement:* ZeroTier requires running a system-level background daemon (`zerotier-one` managed via systemd) with persistent machine identity keys stored in `/var/lib/zerotier-one`. - - *External Orchestration & State:* Joining a ZeroTier network requires a 16-digit network ID issued by a central ZeroTier network controller (either ZeroTier Central cloud or a self-hosted controller daemon) and explicit authorization per node. - - *Namespace Isolation Challenges:* Because `zerotier-one` expects to manage host networking or requires custom multi-instance configuration flags (`-D`), provisioning isolated, ephemeral, zero-leak network pairs in short-lived Linux network namespaces requires extensive external controller automation. +- **Harness Integration:** Automated in `crates/yip-bench/tests/run-iperf-compare.sh` using an offline, netns-local controller via `zerotier-one` and its local control HTTP API (`http://127.0.0.1:9993/controller/network/`), enabling completely air-gapped testing without external planetary server access. - **Design Mapping to yip:** ZeroTier prioritizes global SDN management, access control lists (flow rules), and multi-tenant Layer 2 virtual switching. In contrast, yip focuses on high-performance point-to-point and edge mesh transport survivability. yip operates as a self-contained, configuration-file-driven daemon with zero external controller dependencies, zero cloud phone-home, and integrated Reed–Solomon FEC designed to withstand severe packet loss that degrades ZeroTier's underlying UDP streams. #### 5. AmneziaWG (Censorship-Resistant WireGuard) - **Architecture:** A specialized fork of WireGuard designed to evade Deep Packet Inspection (DPI) and protocol classification firewalls. Standard WireGuard handshakes have rigid 4-byte message types (`0x01` Init, `0x02` Response, `0x03` Cookie) and predictable packet lengths (148 bytes for initiation, 92 bytes for response), making them easily fingerprinted and blocked by stateful DPI systems. AmneziaWG injects random-length junk padding (`InitJunkSize`, `ResponseJunkSize`) and custom random message type headers (`Undercover` mode). -- **Why External & Not Bundled in Standalone Netns Benchmarks:** - - *Custom Kernel Module (DKMS):* The primary high-performance implementation requires compiling and inserting an out-of-tree DKMS kernel module (`amneziawg-dkms`), which requires kernel headers matching the exact running kernel and is not distributed in standard distribution package repositories. - - *Out-of-Tree Userspace Fallback:* The userspace implementation (`amneziawg-go`) requires external Go build dependencies and third-party binaries rather than native distribution packages. -- **Design Mapping to yip:** AmneziaWG addresses **censorship and DPI fingerprinting**, whereas yip addresses **physical and wireless link degradation (loss, jitter, reordering)**. AmneziaWG modifies WireGuard's header layout but retains WireGuard's underlying transport semantics—meaning it inherits WireGuard's vulnerability to packet loss and throughput collapse under lossy conditions. Furthermore, yip inherently protects its packet headers against fingerprinting and tampering using keyed SipHash-2-4 authentication and XOR masking over the wire frame. +- **Harness Integration:** Supported via `awg` CLI and `amneziawg-go` userspace daemon or DKMS kernel module with custom obfuscation headers (`Jc`, `Jmin`, `Jmax`, `S1`, `S2`, `H1..H4`). +- **Design Mapping to yip:** AmneziaWG addresses **censorship and DPI fingerprinting**, whereas yip addresses **both physical link degradation (loss, jitter, reordering) and DPI evasion**. AmneziaWG modifies WireGuard's header layout but retains WireGuard's underlying transport semantics—inheriting WireGuard's severe throughput collapse under packet loss. Furthermore, yip inherently protects its packet headers against fingerprinting and tampering using keyed SipHash-1-3 authentication and XOR masking over the wire frame. + +#### 6. Tailscale (WireGuard-in-Userspace + Headscale Coordinator) +- **Architecture:** Tailscale builds upon WireGuard using an in-userspace Go implementation (`wireguard-go`) integrated into the `tailscaled` daemon. Control plane coordination is typically cloud-managed via DERP relay servers, STUN NAT discovery, and node authorization. +- **Harness Integration:** Benchmarked in isolated `netns` using a local, air-gapped [Headscale](https://github.com/juanfont/headscale) coordination server and ephemeral pre-authenticated registration keys, bypassing public DERP and cloud infrastructure. +- **Behavior Under Loss:** Because Tailscale encapsulates packets using unmodified WireGuard protocols in userspace, it suffers from the same loss-induced throughput degradation as WireGuard, exacerbated slightly by userspace context switches. +- **Design Mapping to yip:** Tailscale solves enterprise identity and zero-trust mesh peering, but delegates transport reliability entirely to underlying UDP/TCP. yip provides transport-native forward error correction, ensuring uninterrupted flow delivery across degraded wireless and WAN paths. + +#### 7. NetBird (WireGuard-in-Userspace + WebRTC Management) +- **Architecture:** NetBird combines WireGuard in userspace with WebRTC ICE / STUN / TURN signaling for direct peer-to-peer traversal and an open-source management server architecture. +- **Harness Integration:** Detected and isolated in dedicated namespaces with graceful skip when local management is not provisioned. +- **Design Mapping to yip:** Similar to Tailscale, NetBird provides centralized overlay orchestration over WireGuard tunnels. yip distinguishes itself by integrating proactive erasure coding (Reed–Solomon over GF(256)) and reactive hybrid ARQ directly into the data plane. + +--- + +### Installing Optional Contenders + +To install or build any of the external contenders for local benchmarking: + +```sh +# Check which contender binaries are currently installed on the host: +./crates/yip-bench/setup-contenders.sh --check + +# Build and install missing contenders (requires Go and build tools): +sudo ./crates/yip-bench/setup-contenders.sh --amneziawg +sudo ./crates/yip-bench/setup-contenders.sh --tailscale +sudo ./crates/yip-bench/setup-contenders.sh --zerotier +sudo ./crates/yip-bench/setup-contenders.sh --netbird + +# Or build all available contenders: +sudo ./crates/yip-bench/setup-contenders.sh --all +``` + +When running `crates/yip-bench/tests/run-iperf-compare.sh`, any contender whose binaries are absent will log `[contender] SKIP: binary not found` and continue testing without failure. + +--- + +### Dual TCP and UDP-Under-Loss Evaluation + +The benchmark runner executes both TCP stream goodput and UDP loss recovery evaluation: +```sh +# Standard sweep: ping (50 samples), TCP goodput (iperf3 -t 8), and UDP throughput/loss (50 Mbit/s offered): +sudo bash crates/yip-bench/tests/run-iperf-compare.sh + +# High-precision sweep with custom UDP offered rate: +sudo PING_COUNT=1000 UDP_RATE=100M bash crates/yip-bench/tests/run-iperf-compare.sh +``` --- diff --git a/crates/yip-bench/setup-contenders.sh b/crates/yip-bench/setup-contenders.sh new file mode 100755 index 00000000..552be97a --- /dev/null +++ b/crates/yip-bench/setup-contenders.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# setup-contenders.sh — Turnkey helper to install or build optional benchmark contenders: +# AmneziaWG, ZeroTier, Tailscale (with Headscale coordinator), and NetBird. +# +# Usage: +# sudo ./setup-contenders.sh --all +# sudo ./setup-contenders.sh --amneziawg +# sudo ./setup-contenders.sh --zerotier +# sudo ./setup-contenders.sh --tailscale +# sudo ./setup-contenders.sh --netbird +# ./setup-contenders.sh --check +set -euo pipefail + +TARGET_BIN_DIR="/usr/local/bin" + +log() { echo -e "\033[1;34m[setup-contenders]\033[0m $*"; } +warn() { echo -e "\033[1;33m[setup-contenders:WARN]\033[0m $*"; } +err() { echo -e "\033[1;31m[setup-contenders:ERROR]\033[0m $*" >&2; exit 1; } + +check_status() { + log "Checking contender tool availability:" + printf " %-12s : " "WireGuard" + command -v wg >/dev/null 2>&1 && echo "INSTALLED ($(which wg))" || echo "MISSING" + printf " %-12s : " "OpenVPN" + command -v openvpn >/dev/null 2>&1 && echo "INSTALLED ($(which openvpn))" || echo "MISSING" + printf " %-12s : " "n2n" + (command -v edge >/dev/null 2>&1 && command -v supernode >/dev/null 2>&1) && echo "INSTALLED ($(which edge))" || echo "MISSING" + printf " %-12s : " "AmneziaWG" + (command -v awg >/dev/null 2>&1 && (command -v amneziawg-go >/dev/null 2>&1 || modprobe amneziawg 2>/dev/null)) && echo "INSTALLED ($(which awg))" || echo "MISSING" + printf " %-12s : " "ZeroTier" + command -v zerotier-one >/dev/null 2>&1 && echo "INSTALLED ($(which zerotier-one))" || echo "MISSING" + printf " %-12s : " "Tailscale" + (command -v tailscaled >/dev/null 2>&1 && command -v headscale >/dev/null 2>&1) && echo "INSTALLED" || echo "MISSING" + printf " %-12s : " "NetBird" + command -v netbird >/dev/null 2>&1 && echo "INSTALLED ($(which netbird))" || echo "MISSING" +} + +setup_amneziawg() { + log "Building AmneziaWG (amneziawg-go & awg-tools)..." + if ! command -v go >/dev/null 2>&1; then err "Go toolchain required to build AmneziaWG"; fi + local tmp; tmp="$(mktemp -d /tmp/build-awg.XXXXXX)" + pushd "$tmp" >/dev/null + log "Compiling amneziawg-go..." + GOBIN="$TARGET_BIN_DIR" go install github.com/amnezia-vpn/amneziawg-go@latest || warn "amneziawg-go compile failed" + log "Cloning and building awg CLI tool..." + if command -v git >/dev/null 2>&1 && command -v make >/dev/null 2>&1; then + git clone --depth 1 https://github.com/amnezia-vpn/amneziawg-tools.git awg-tools 2>/dev/null || true + if [ -d awg-tools/src ]; then + make -C awg-tools/src + install -m 755 awg-tools/src/wg "$TARGET_BIN_DIR/awg" + fi + else + warn "git/make missing; cannot build awg CLI tool from source" + fi + popd >/dev/null + rm -rf "$tmp" + log "AmneziaWG setup step complete." +} + +setup_zerotier() { + log "Installing ZeroTier..." + if command -v zerotier-one >/dev/null 2>&1; then + log "ZeroTier is already installed ($(which zerotier-one))" + return + fi + if command -v apt-get >/dev/null 2>&1; then + curl -s 'https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/doc/contact%40zerotier.com.gpg' | gpg --dearmor -o /usr/share/keyrings/zerotierone-archive-keyring.gpg 2>/dev/null || true + apt-get update && apt-get install -y zerotier-one || warn "apt-get install zerotier-one failed" + elif command -v curl >/dev/null 2>&1; then + curl -s https://install.zerotier.com | bash || warn "ZeroTier quick install script failed" + else + warn "Package manager or curl not found; please install zerotier-one manually." + fi +} + +setup_tailscale() { + log "Setting up Tailscale & Headscale..." + if ! command -v go >/dev/null 2>&1; then err "Go toolchain required to build Tailscale & Headscale"; fi + log "Compiling tailscale & tailscaled..." + GOBIN="$TARGET_BIN_DIR" go install tailscale.com/cmd/tailscale@latest || warn "tailscale build failed" + GOBIN="$TARGET_BIN_DIR" go install tailscale.com/cmd/tailscaled@latest || warn "tailscaled build failed" + log "Compiling headscale (local air-gapped coordinator)..." + GOBIN="$TARGET_BIN_DIR" go install github.com/juanfont/headscale/cmd/headscale@latest || warn "headscale build failed" + log "Tailscale / Headscale setup step complete." +} + +setup_netbird() { + log "Setting up NetBird..." + if ! command -v go >/dev/null 2>&1; then err "Go toolchain required to build NetBird"; fi + log "Compiling netbird..." + GOBIN="$TARGET_BIN_DIR" go install github.com/netbirdio/netbird@latest || warn "netbird build failed" + log "NetBird setup step complete." +} + +# ── Entrypoint ──────────────────────────────────────────────────────────────── +if [ $# -eq 0 ] || [ "${1:-}" = "--check" ]; then + check_status + exit 0 +fi + +while [ $# -gt 0 ]; do + case "$1" in + --all) + setup_amneziawg + setup_zerotier + setup_tailscale + setup_netbird + ;; + --amneziawg) + setup_amneziawg + ;; + --zerotier) + setup_zerotier + ;; + --tailscale) + setup_tailscale + ;; + --netbird) + setup_netbird + ;; + --check) + check_status + ;; + *) + echo "Unknown argument: $1" + echo "Usage: $0 [--all | --amneziawg | --zerotier | --tailscale | --netbird | --check]" + exit 1 + ;; + esac + shift +done + +log "Selected contenders processed. Re-checking status:" +check_status diff --git a/crates/yip-bench/tests/run-iperf-compare.sh b/crates/yip-bench/tests/run-iperf-compare.sh index 5ed447f6..dfb27a3a 100755 --- a/crates/yip-bench/tests/run-iperf-compare.sh +++ b/crates/yip-bench/tests/run-iperf-compare.sh @@ -1,17 +1,17 @@ #!/usr/bin/env bash -# run-iperf-compare.sh — iperf3 TCP throughput + ping latency/loss under netem, -# across the full-IP tunnels: yip, WireGuard, OpenVPN, n2n. +# run-iperf-compare.sh — iperf3 TCP & UDP throughput + ping latency/loss under netem, +# across contemporary tunnels and mesh overlays: +# yip, WireGuard, AmneziaWG, OpenVPN, n2n, ZeroTier, Tailscale, NetBird. # -# Each contender is set up in its OWN netns pair (mirroring the verified spikes); -# at each loss rate (0 5 10 %, symmetric delay 5ms) we run: -# ping -c 50 -i 0.1 -> effective loss % + avg RTT (ms) -# iperf3 -c -t 8 -> TCP throughput (Mbit/s) +# Each contender is set up in its OWN dedicated netns pair; at each loss rate +# (0 5 10 %, symmetric delay 5ms) we run: +# ping -c 50 -i 0.1 -> effective loss % + avg RTT (ms) +# iperf3 -c -t 8 -> TCP goodput (Mbit/s) +# iperf3 -u -c -b -t 8 -> UDP throughput (Mbit/s) + UDP loss % # # yip MUST always run; every other contender SKIPs cleanly (logging why) if its # tool/module is absent. # -# n2n note: one TAP data plane serves both L2 and L3 — measured once. -# # Usage: run-iperf-compare.sh [] set -euo pipefail @@ -63,6 +63,31 @@ OVL_NA_IP="10.17.0.1"; OVL_NB_IP="10.17.0.2" N2N_PORT="7654" N2N_AVAILABLE=true +# ── AmneziaWG ───────────────────────────────────────────────────────────────── +NS_AA="ipfAA"; NS_AB="ipfAB"; VAA="ipfvAA"; VAB="ipfvAB" +VAA_IP="10.18.0.1"; VAB_IP="10.18.0.2" +TUN_AA_IP="10.19.0.1"; TUN_AB_IP="10.19.0.2" +APORT_A="51870"; APORT_B="51871" +AWG_AVAILABLE=true + +# ── ZeroTier ────────────────────────────────────────────────────────────────── +NS_ZA="ipfZA"; NS_ZB="ipfZB"; VZA="ipfvZA"; VZB="ipfvZB" +VZA_IP="10.20.0.1"; VZB_IP="10.20.0.2" +TUN_ZA_IP="10.21.0.1"; TUN_ZB_IP="10.21.0.2" +ZT_AVAILABLE=true + +# ── Tailscale ───────────────────────────────────────────────────────────────── +NS_TA="ipfTA"; NS_TB="ipfTB"; VTA="ipfvTA"; VTB="ipfvTB" +VTA_IP="10.22.0.1"; VTB_IP="10.22.0.2" +TUN_TA_IP="10.23.0.1"; TUN_TB_IP="10.23.0.2" +TS_AVAILABLE=true + +# ── NetBird ─────────────────────────────────────────────────────────────────── +NS_BA="ipfBA"; NS_BB="ipfBB"; VBA="ipfvBA"; VBB="ipfvBB" +VBA_IP="10.24.0.1"; VBB_IP="10.24.0.2" +TUN_BA_IP="10.25.0.1"; TUN_BB_IP="10.25.0.2" +NB_AVAILABLE=true + # ── cleanup ─────────────────────────────────────────────────────────────────── cleanup() { echo "[cleanup] killing daemons and removing namespaces" @@ -71,10 +96,13 @@ cleanup() { sleep 0.2 [ -n "$PID_YA" ] && kill -9 "$PID_YA" 2>/dev/null || true [ -n "$PID_YB" ] && kill -9 "$PID_YB" 2>/dev/null || true - # Remove WG interfaces before deleting namespaces. + # Remove virtual interfaces before deleting namespaces ip netns exec "$NS_WA" ip link del wg0 2>/dev/null || true ip netns exec "$NS_WB" ip link del wg0 2>/dev/null || true - for ns in "$NS_YA" "$NS_YB" "$NS_WA" "$NS_WB" "$NS_OA" "$NS_OB" "$NS_NA" "$NS_NB"; do + ip netns exec "$NS_AA" ip link del awg0 2>/dev/null || true + ip netns exec "$NS_AB" ip link del awg0 2>/dev/null || true + for ns in "$NS_YA" "$NS_YB" "$NS_WA" "$NS_WB" "$NS_OA" "$NS_OB" "$NS_NA" "$NS_NB" \ + "$NS_AA" "$NS_AB" "$NS_ZA" "$NS_ZB" "$NS_TA" "$NS_TB" "$NS_BA" "$NS_BB"; do ip netns pids "$ns" 2>/dev/null | xargs -r kill -9 2>/dev/null || true ip netns del "$ns" 2>/dev/null || true done @@ -94,8 +122,14 @@ make_pair() { ip netns exec "$nsS" ip link set "$vS" up; ip netns exec "$nsS" ip link set lo up } +# ── helper: symmetric netem loss + 5ms delay ────────────────────────────────── apply_netem() { local nsR="$1" nsS="$2" vR="$3" vS="$4" loss="$5" + if [ "$loss" -eq 0 ]; then + ip netns exec "$nsR" tc qdisc del dev "$vR" root 2>/dev/null || true + ip netns exec "$nsS" tc qdisc del dev "$vS" root 2>/dev/null || true + return + fi ip netns exec "$nsR" tc qdisc replace dev "$vR" root netem loss "${loss}%" delay 5ms ip netns exec "$nsS" tc qdisc replace dev "$vS" root netem loss "${loss}%" delay 5ms } @@ -132,7 +166,6 @@ measure_iperf() { sleep 0.5 mbps="$(ip netns exec "$ns_cli" iperf3 -c "$srv_ip" -t 8 2>/dev/null \ | grep -i receiver | grep -oP '[0-9.]+ (Mbits|Gbits)/sec' | tail -1 || echo "")" - # Normalize Gbits -> Mbits. if echo "$mbps" | grep -qi 'Gbits'; then mbps="$(echo "$mbps" | grep -oP '[0-9.]+' | awk '{printf "%.1f", $1*1000}')" else @@ -142,6 +175,38 @@ measure_iperf() { echo "$mbps" } +# ── helper: iperf3 UDP Mbit/s and loss %; server in bound to , +# client in -> . echo " " ─────────────────── +measure_iperf_udp() { + local ns_srv="$1" srv_ip="$2" ns_cli="$3" out mbps loss + ip netns exec "$ns_srv" iperf3 -s -1 -B "$srv_ip" >/dev/null 2>&1 & + sleep 0.5 + out="$(ip netns exec "$ns_cli" iperf3 -c "$srv_ip" -u -b "${UDP_RATE:-50M}" -t 8 -J 2>/dev/null || true)" + if [ -n "$out" ]; then + if command -v jq >/dev/null 2>&1; then + mbps="$(echo "$out" | jq -r '.end.sum.bits_per_second // empty' 2>/dev/null || true)" + loss="$(echo "$out" | jq -r '.end.sum.lost_percent // empty' 2>/dev/null || true)" + else + mbps="$(echo "$out" | grep -oP '"bits_per_second":\s*[0-9.]+' | tail -1 | grep -oP '[0-9.]+' || true)" + loss="$(echo "$out" | grep -oP '"lost_percent":\s*[0-9.]+' | tail -1 | grep -oP '[0-9.]+' || true)" + fi + if [ -n "$mbps" ] && [ "$mbps" != "null" ]; then + mbps="$(awk "BEGIN {printf \"%.1f\", $mbps / 1000000}")" + else + mbps="N/A" + fi + if [ -n "$loss" ] && [ "$loss" != "null" ]; then + loss="$(awk "BEGIN {printf \"%.1f\", $loss}")" + else + loss="N/A" + fi + else + mbps="N/A" + loss="N/A" + fi + echo "$mbps $loss" +} + # ══════════════════════════════════════════════════════════════════════════════ # yip setup (always) # ══════════════════════════════════════════════════════════════════════════════ @@ -218,65 +283,148 @@ setup_wg() { echo "[wg] SKIP: modprobe wireguard failed"; WG_AVAILABLE=false; return fi echo "[wg] generating keypairs" - local pa pb ka kb - pa="$(wg genkey)"; ka="$(echo "$pa" | wg pubkey)" - pb="$(wg genkey)"; kb="$(echo "$pb" | wg pubkey)" + local privA pubA privB pubB + privA="$(wg genkey)"; pubA="$(echo "$privA" | wg pubkey)" + privB="$(wg genkey)"; pubB="$(echo "$privB" | wg pubkey)" echo "[wg] creating namespaces and veth pair" make_pair "$NS_WA" "$NS_WB" "$VWA" "$VWB" "$VWA_IP" "$VWB_IP" ip netns exec "$NS_WA" ip link add wg0 type wireguard ip netns exec "$NS_WB" ip link add wg0 type wireguard - printf '%s' "$pa" > "$TMPDIR_TEST/wg_a"; printf '%s' "$pb" > "$TMPDIR_TEST/wg_b" - chmod 600 "$TMPDIR_TEST/wg_a" "$TMPDIR_TEST/wg_b" - ip netns exec "$NS_WA" wg set wg0 private-key "$TMPDIR_TEST/wg_a" listen-port "$WPORT_A" \ - peer "$kb" allowed-ips "${TUN_WB_IP}/32" endpoint "${VWB_IP}:${WPORT_B}" - ip netns exec "$NS_WB" wg set wg0 private-key "$TMPDIR_TEST/wg_b" listen-port "$WPORT_B" \ - peer "$ka" allowed-ips "${TUN_WA_IP}/32" endpoint "${VWA_IP}:${WPORT_A}" ip netns exec "$NS_WA" ip addr add "${TUN_WA_IP}/24" dev wg0 - ip netns exec "$NS_WA" ip link set wg0 up ip netns exec "$NS_WB" ip addr add "${TUN_WB_IP}/24" dev wg0 + echo "$privA" > "$TMPDIR_TEST/wgA.key" + echo "$privB" > "$TMPDIR_TEST/wgB.key" + ip netns exec "$NS_WA" wg set wg0 listen-port "$WPORT_A" private-key "$TMPDIR_TEST/wgA.key" \ + peer "$pubB" endpoint "${VWB_IP}:${WPORT_B}" allowed-ips "${TUN_WB_IP}/32" + ip netns exec "$NS_WB" wg set wg0 listen-port "$WPORT_B" private-key "$TMPDIR_TEST/wgB.key" \ + peer "$pubA" endpoint "${VWA_IP}:${WPORT_A}" allowed-ips "${TUN_WA_IP}/32" + ip netns exec "$NS_WA" ip link set wg0 up ip netns exec "$NS_WB" ip link set wg0 up sleep 0.5 - if ! ip netns exec "$NS_WB" ping -c 3 -W 5 "$TUN_WA_IP" >/dev/null; then - echo "[wg] SKIP: baseline WG ping failed"; WG_AVAILABLE=false; return + if ! ip netns exec "$NS_WB" ping -c 3 -W 5 "$TUN_WA_IP" >/dev/null 2>&1; then + echo "[wg] SKIP: baseline ping failed"; WG_AVAILABLE=false; return fi echo "[wg] tunnel up" } setup_wg # ══════════════════════════════════════════════════════════════════════════════ -# OpenVPN setup (static-key p2p TUN; AES-256-CBC since static key has no AEAD) +# AmneziaWG setup (kernel module or amneziawg-go) +# ══════════════════════════════════════════════════════════════════════════════ +setup_awg() { + if ! command -v awg >/dev/null 2>&1; then + echo "[awg] SKIP: 'awg' command not found"; AWG_AVAILABLE=false; return + fi + local use_go=false + if ! modprobe amneziawg 2>/dev/null; then + if command -v amneziawg-go >/dev/null 2>&1; then + use_go=true + else + echo "[awg] SKIP: neither amneziawg kernel module nor amneziawg-go found" + AWG_AVAILABLE=false; return + fi + fi + echo "[awg] generating keypairs" + local privA pubA privB pubB + privA="$(awg genkey)"; pubA="$(echo "$privA" | awg pubkey)" + privB="$(awg genkey)"; pubB="$(echo "$privB" | awg pubkey)" + echo "[awg] creating namespaces and veth pair" + make_pair "$NS_AA" "$NS_AB" "$VAA" "$VAB" "$VAA_IP" "$VAB_IP" + if [ "$use_go" = true ]; then + ip netns exec "$NS_AA" amneziawg-go awg0 >"$TMPDIR_TEST/awg-go-a.log" 2>&1 & + ip netns exec "$NS_AB" amneziawg-go awg0 >"$TMPDIR_TEST/awg-go-b.log" 2>&1 & + sleep 0.5 + else + ip netns exec "$NS_AA" ip link add awg0 type amneziawg + ip netns exec "$NS_AB" ip link add awg0 type amneziawg + fi + ip netns exec "$NS_AA" ip addr add "${TUN_AA_IP}/24" dev awg0 + ip netns exec "$NS_AB" ip addr add "${TUN_AB_IP}/24" dev awg0 + + local CFG_AA="$TMPDIR_TEST/awgA.conf"; local CFG_AB="$TMPDIR_TEST/awgB.conf" + cat > "$CFG_AA" < "$CFG_AB" </dev/null 2>&1; then + echo "[awg] SKIP: baseline ping failed"; AWG_AVAILABLE=false; return + fi + echo "[awg] tunnel up" +} +setup_awg + +# ══════════════════════════════════════════════════════════════════════════════ +# OpenVPN setup (peer-fingerprint, UDP, AES-256-GCM) # ══════════════════════════════════════════════════════════════════════════════ setup_ovpn() { if ! command -v openvpn >/dev/null 2>&1; then echo "[ovpn] SKIP: 'openvpn' not found"; OVPN_AVAILABLE=false; return fi - if ! command -v openssl >/dev/null 2>&1; then - echo "[ovpn] SKIP: 'openssl' not found (needed for TLS certs)"; OVPN_AVAILABLE=false; return - fi + echo "[ovpn] generating self-signed certificates" + openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ + -keyout "$TMPDIR_TEST/ovpn-a.key" -out "$TMPDIR_TEST/ovpn-a.crt" \ + -days 1 -nodes -subj "/CN=ovpn-a" >/dev/null 2>&1 + openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ + -keyout "$TMPDIR_TEST/ovpn-b.key" -out "$TMPDIR_TEST/ovpn-b.crt" \ + -days 1 -nodes -subj "/CN=ovpn-b" >/dev/null 2>&1 + local FA FB + FA="$(openssl x509 -in "$TMPDIR_TEST/ovpn-a.crt" -noout -fingerprint -sha256 \ + | cut -d= -f2)" + FB="$(openssl x509 -in "$TMPDIR_TEST/ovpn-b.crt" -noout -fingerprint -sha256 \ + | cut -d= -f2)" + echo "[ovpn] creating namespaces and veth pair" make_pair "$NS_OA" "$NS_OB" "$VOA" "$VOB" "$VOA_IP" "$VOB_IP" - # Modern fair config: TLS + AES-256-GCM (AEAD) via peer-fingerprint mode (no - # PKI server needed). Static-key/CBC would understate OpenVPN — its DCO+GCM - # data channel is its real deployed performance. - echo "[ovpn] generating self-signed EC certs (peer-fingerprint mode)" - if ! openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ - -keyout "$TMPDIR_TEST/ovpn-a.key" -out "$TMPDIR_TEST/ovpn-a.crt" \ - -days 2 -nodes -subj /CN=ovpn-a >/dev/null 2>&1 \ - || ! openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ - -keyout "$TMPDIR_TEST/ovpn-b.key" -out "$TMPDIR_TEST/ovpn-b.crt" \ - -days 2 -nodes -subj /CN=ovpn-b >/dev/null 2>&1; then - echo "[ovpn] SKIP: openssl cert generation failed"; OVPN_AVAILABLE=false; return - fi - local FA FB - FA=$(openssl x509 -in "$TMPDIR_TEST/ovpn-a.crt" -noout -fingerprint -sha256 | sed 's/.*=//') - FB=$(openssl x509 -in "$TMPDIR_TEST/ovpn-b.crt" -noout -fingerprint -sha256 | sed 's/.*=//') - ip netns exec "$NS_OA" openvpn --dev tun --dev-type tun --local "$VOA_IP" --lport 1194 \ - --remote "$VOB_IP" --rport 1194 --ifconfig "$TUN_OA_IP" "$TUN_OB_IP" \ - --tls-server --dh none --cert "$TMPDIR_TEST/ovpn-a.crt" --key "$TMPDIR_TEST/ovpn-a.key" \ + ip netns exec "$NS_OA" openvpn \ + --dev tun0 --dev-type tun \ + --local "$VOA_IP" --port 1194 --ifconfig "$TUN_OA_IP" "$TUN_OB_IP" \ + --tls-server --cert "$TMPDIR_TEST/ovpn-a.crt" --key "$TMPDIR_TEST/ovpn-a.key" \ --peer-fingerprint "$FB" --data-ciphers AES-256-GCM \ --proto udp --verb 1 --ping 10 --ping-restart 0 \ >"$TMPDIR_TEST/ovpn-a.log" 2>&1 & - ip netns exec "$NS_OB" openvpn --dev tun --dev-type tun --local "$VOB_IP" --lport 1194 \ + ip netns exec "$NS_OB" openvpn \ + --dev tun0 --dev-type tun \ --remote "$VOA_IP" --rport 1194 --ifconfig "$TUN_OB_IP" "$TUN_OA_IP" \ --tls-client --cert "$TMPDIR_TEST/ovpn-b.crt" --key "$TMPDIR_TEST/ovpn-b.key" \ --peer-fingerprint "$FA" --data-ciphers AES-256-GCM \ @@ -329,6 +477,124 @@ setup_n2n() { } setup_n2n +# ══════════════════════════════════════════════════════════════════════════════ +# ZeroTier setup (local netns controller) +# ══════════════════════════════════════════════════════════════════════════════ +setup_zerotier() { + if ! command -v zerotier-one >/dev/null 2>&1; then + echo "[zerotier] SKIP: 'zerotier-one' not found"; ZT_AVAILABLE=false; return + fi + echo "[zerotier] creating namespaces and veth pair" + make_pair "$NS_ZA" "$NS_ZB" "$VZA" "$VZB" "$VZA_IP" "$VZB_IP" + local zt_dir_a="$TMPDIR_TEST/zt_a"; local zt_dir_b="$TMPDIR_TEST/zt_b" + mkdir -p "$zt_dir_a" "$zt_dir_b" + ip netns exec "$NS_ZA" zerotier-one "$zt_dir_a" >"$TMPDIR_TEST/zt-a.log" 2>&1 & + ip netns exec "$NS_ZB" zerotier-one "$zt_dir_b" >"$TMPDIR_TEST/zt-b.log" 2>&1 & + local i token_a="" node_a="" + for i in $(seq 1 30); do + if [ -f "$zt_dir_a/authtoken.secret" ] && [ -f "$zt_dir_b/authtoken.secret" ]; then + token_a="$(cat "$zt_dir_a/authtoken.secret")" + break + fi + sleep 0.2 + done + if [ -z "$token_a" ]; then + echo "[zerotier] SKIP: daemon failed to initialize"; ZT_AVAILABLE=false; return + fi + node_a="$(ip netns exec "$NS_ZA" zerotier-cli -D"$zt_dir_a" info 2>/dev/null | awk '{print $3}' || true)" + if [ -z "$node_a" ]; then + echo "[zerotier] SKIP: failed to get node identity"; ZT_AVAILABLE=false; return + fi + local net_id="${node_a}ffffff" + ip netns exec "$NS_ZA" curl -s -H "X-ZT1-Auth: $token_a" -X POST \ + -d '{"name": "bench", "private": false, "v4AssignMode": {"zt": true}, "routes": [{"target": "10.21.0.0/24"}], "ipAssignmentPools": [{"ipRangeStart": "10.21.0.1", "ipRangeEnd": "10.21.0.254"}]}' \ + "http://127.0.0.1:9993/controller/network/${net_id}" >/dev/null 2>&1 || true + ip netns exec "$NS_ZA" zerotier-cli -D"$zt_dir_a" join "$net_id" >/dev/null 2>&1 || true + ip netns exec "$NS_ZB" zerotier-cli -D"$zt_dir_b" join "$net_id" >/dev/null 2>&1 || true + local found=false + for i in $(seq 1 40); do + if ip netns exec "$NS_ZA" ip -o -4 addr show 2>/dev/null | grep -q "10.21." && \ + ip netns exec "$NS_ZB" ip -o -4 addr show 2>/dev/null | grep -q "10.21."; then + found=true; break + fi + sleep 0.5 + done + if [ "$found" = false ]; then + echo "[zerotier] SKIP: overlay assignment timed out"; ZT_AVAILABLE=false; return + fi + echo "[zerotier] network up" +} +setup_zerotier + +# ══════════════════════════════════════════════════════════════════════════════ +# Tailscale setup (local headscale coordinator) +# ══════════════════════════════════════════════════════════════════════════════ +setup_tailscale() { + if ! command -v tailscaled >/dev/null 2>&1 || ! command -v headscale >/dev/null 2>&1; then + echo "[tailscale] SKIP: 'tailscaled' or 'headscale' not found"; TS_AVAILABLE=false; return + fi + echo "[tailscale] creating namespaces and veth pair" + make_pair "$NS_TA" "$NS_TB" "$VTA" "$VTB" "$VTA_IP" "$VTB_IP" + local hs_dir="$TMPDIR_TEST/headscale" + mkdir -p "$hs_dir" + cat > "$hs_dir/config.yaml" <"$TMPDIR_TEST/headscale.log" 2>&1 & + sleep 1 + ip netns exec "$NS_TA" headscale users create bench -c "$hs_dir/config.yaml" >/dev/null 2>&1 || true + local auth_key + auth_key="$(ip netns exec "$NS_TA" headscale preauthkeys create -u bench --reusable --expiration 1h -c "$hs_dir/config.yaml" 2>/dev/null || true)" + if [ -z "$auth_key" ]; then + echo "[tailscale] SKIP: headscale preauthkey creation failed"; TS_AVAILABLE=false; return + fi + local ts_dir_a="$TMPDIR_TEST/ts_a"; local ts_dir_b="$TMPDIR_TEST/ts_b" + mkdir -p "$ts_dir_a" "$ts_dir_b" + ip netns exec "$NS_TA" tailscaled --state="$ts_dir_a/tailscaled.state" --socket="$ts_dir_a/tailscaled.sock" --tun=tailscale0 >"$TMPDIR_TEST/ts-a.log" 2>&1 & + ip netns exec "$NS_TB" tailscaled --state="$ts_dir_b/tailscaled.state" --socket="$ts_dir_b/tailscaled.sock" --tun=tailscale0 >"$TMPDIR_TEST/ts-b.log" 2>&1 & + sleep 1 + ip netns exec "$NS_TA" tailscale --socket="$ts_dir_a/tailscaled.sock" up --login-server="http://${VTA_IP}:8080" --auth-key="$auth_key" >/dev/null 2>&1 || true + ip netns exec "$NS_TB" tailscale --socket="$ts_dir_b/tailscaled.sock" up --login-server="http://${VTA_IP}:8080" --auth-key="$auth_key" >/dev/null 2>&1 || true + local found=false + for i in $(seq 1 40); do + if ip netns exec "$NS_TA" ip -o -4 addr show tailscale0 >/dev/null 2>&1 && \ + ip netns exec "$NS_TB" ip -o -4 addr show tailscale0 >/dev/null 2>&1; then + found=true; break + fi + sleep 0.5 + done + if [ "$found" = false ]; then + echo "[tailscale] SKIP: tailscale tunnel activation timed out"; TS_AVAILABLE=false; return + fi + echo "[tailscale] tunnel up" +} +setup_tailscale + +# ══════════════════════════════════════════════════════════════════════════════ +# NetBird setup (management coordinator) +# ══════════════════════════════════════════════════════════════════════════════ +setup_netbird() { + if ! command -v netbird >/dev/null 2>&1; then + echo "[netbird] SKIP: 'netbird' not found"; NB_AVAILABLE=false; return + fi + echo "[netbird] creating namespaces and veth pair" + make_pair "$NS_BA" "$NS_BB" "$VBA" "$VBB" "$VBA_IP" "$VBB_IP" + echo "[netbird] SKIP: local management service not running"; NB_AVAILABLE=false +} +setup_netbird + # ══════════════════════════════════════════════════════════════════════════════ # Sweep # ══════════════════════════════════════════════════════════════════════════════ @@ -336,7 +602,7 @@ LOSS_RATES="0 5 10" echo "" echo "==========================================================" -echo " iperf3 TCP throughput + ping latency/loss under netem" +echo " iperf3 TCP & UDP throughput + ping latency/loss under netem" echo " ping -c ${PING_COUNT} -i ${PING_INTERVAL}; iperf3 -t 8; netem: loss X% delay 5ms (symmetric)" echo "==========================================================" @@ -344,53 +610,105 @@ for LOSS in $LOSS_RATES; do echo "" echo "[netem] loss=${LOSS}% delay=5ms on all active pairs" apply_netem "$NS_YA" "$NS_YB" "$VYA" "$VYB" "$LOSS" - [ "$WG_AVAILABLE" = true ] && apply_netem "$NS_WA" "$NS_WB" "$VWA" "$VWB" "$LOSS" + [ "$WG_AVAILABLE" = true ] && apply_netem "$NS_WA" "$NS_WB" "$VWA" "$VWB" "$LOSS" + [ "$AWG_AVAILABLE" = true ] && apply_netem "$NS_AA" "$NS_AB" "$VAA" "$VAB" "$LOSS" [ "$OVPN_AVAILABLE" = true ] && apply_netem "$NS_OA" "$NS_OB" "$VOA" "$VOB" "$LOSS" - [ "$N2N_AVAILABLE" = true ] && apply_netem "$NS_NA" "$NS_NB" "$VNA" "$VNB" "$LOSS" + [ "$N2N_AVAILABLE" = true ] && apply_netem "$NS_NA" "$NS_NB" "$VNA" "$VNB" "$LOSS" + [ "$ZT_AVAILABLE" = true ] && apply_netem "$NS_ZA" "$NS_ZB" "$VZA" "$VZB" "$LOSS" + [ "$TS_AVAILABLE" = true ] && apply_netem "$NS_TA" "$NS_TB" "$VTA" "$VTB" "$LOSS" + [ "$NB_AVAILABLE" = true ] && apply_netem "$NS_BA" "$NS_BB" "$VBA" "$VBB" "$LOSS" # yip read -r Y_LOSS Y_RTT <<< "$(measure_ping "$NS_YB" "$TUN_YA_IP")" - Y_MBPS="$(measure_iperf "$NS_YA" "$TUN_YA_IP" "$NS_YB")" - echo "[yip] loss=${Y_LOSS}% rtt=${Y_RTT}ms tcp=${Y_MBPS}Mbit/s" + Y_TCP="$(measure_iperf "$NS_YA" "$TUN_YA_IP" "$NS_YB")" + read -r Y_UDP_MBPS Y_UDP_LOSS <<< "$(measure_iperf_udp "$NS_YA" "$TUN_YA_IP" "$NS_YB")" + echo "[yip] ping_loss=${Y_LOSS}% rtt=${Y_RTT}ms tcp=${Y_TCP}Mbit/s udp=${Y_UDP_MBPS}Mbit/s udp_loss=${Y_UDP_LOSS}%" # WireGuard - W_LOSS="N/A"; W_RTT="N/A"; W_MBPS="N/A" + W_LOSS="N/A"; W_RTT="N/A"; W_TCP="N/A"; W_UDP_MBPS="N/A"; W_UDP_LOSS="N/A" if [ "$WG_AVAILABLE" = true ]; then read -r W_LOSS W_RTT <<< "$(measure_ping "$NS_WB" "$TUN_WA_IP")" - W_MBPS="$(measure_iperf "$NS_WA" "$TUN_WA_IP" "$NS_WB")" - echo "[wg] loss=${W_LOSS}% rtt=${W_RTT}ms tcp=${W_MBPS}Mbit/s" + W_TCP="$(measure_iperf "$NS_WA" "$TUN_WA_IP" "$NS_WB")" + read -r W_UDP_MBPS W_UDP_LOSS <<< "$(measure_iperf_udp "$NS_WA" "$TUN_WA_IP" "$NS_WB")" + echo "[wg] ping_loss=${W_LOSS}% rtt=${W_RTT}ms tcp=${W_TCP}Mbit/s udp=${W_UDP_MBPS}Mbit/s udp_loss=${W_UDP_LOSS}%" + fi + + # AmneziaWG + A_LOSS="N/A"; A_RTT="N/A"; A_TCP="N/A"; A_UDP_MBPS="N/A"; A_UDP_LOSS="N/A" + if [ "$AWG_AVAILABLE" = true ]; then + read -r A_LOSS A_RTT <<< "$(measure_ping "$NS_AB" "$TUN_AA_IP")" + A_TCP="$(measure_iperf "$NS_AA" "$TUN_AA_IP" "$NS_AB")" + read -r A_UDP_MBPS A_UDP_LOSS <<< "$(measure_iperf_udp "$NS_AA" "$TUN_AA_IP" "$NS_AB")" + echo "[awg] ping_loss=${A_LOSS}% rtt=${A_RTT}ms tcp=${A_TCP}Mbit/s udp=${A_UDP_MBPS}Mbit/s udp_loss=${A_UDP_LOSS}%" fi # OpenVPN - O_LOSS="N/A"; O_RTT="N/A"; O_MBPS="N/A" + O_LOSS="N/A"; O_RTT="N/A"; O_TCP="N/A"; O_UDP_MBPS="N/A"; O_UDP_LOSS="N/A" if [ "$OVPN_AVAILABLE" = true ]; then read -r O_LOSS O_RTT <<< "$(measure_ping "$NS_OB" "$TUN_OA_IP")" - O_MBPS="$(measure_iperf "$NS_OA" "$TUN_OA_IP" "$NS_OB")" - echo "[ovpn] loss=${O_LOSS}% rtt=${O_RTT}ms tcp=${O_MBPS}Mbit/s" + O_TCP="$(measure_iperf "$NS_OA" "$TUN_OA_IP" "$NS_OB")" + read -r O_UDP_MBPS O_UDP_LOSS <<< "$(measure_iperf_udp "$NS_OA" "$TUN_OA_IP" "$NS_OB")" + echo "[ovpn] ping_loss=${O_LOSS}% rtt=${O_RTT}ms tcp=${O_TCP}Mbit/s udp=${O_UDP_MBPS}Mbit/s udp_loss=${O_UDP_LOSS}%" fi # n2n - N_LOSS="N/A"; N_RTT="N/A"; N_MBPS="N/A" + N_LOSS="N/A"; N_RTT="N/A"; N_TCP="N/A"; N_UDP_MBPS="N/A"; N_UDP_LOSS="N/A" if [ "$N2N_AVAILABLE" = true ]; then read -r N_LOSS N_RTT <<< "$(measure_ping "$NS_NB" "$OVL_NA_IP")" - N_MBPS="$(measure_iperf "$NS_NA" "$OVL_NA_IP" "$NS_NB")" - echo "[n2n] loss=${N_LOSS}% rtt=${N_RTT}ms tcp=${N_MBPS}Mbit/s" + N_TCP="$(measure_iperf "$NS_NA" "$OVL_NA_IP" "$NS_NB")" + read -r N_UDP_MBPS N_UDP_LOSS <<< "$(measure_iperf_udp "$NS_NA" "$OVL_NA_IP" "$NS_NB")" + echo "[n2n] ping_loss=${N_LOSS}% rtt=${N_RTT}ms tcp=${N_TCP}Mbit/s udp=${N_UDP_MBPS}Mbit/s udp_loss=${N_UDP_LOSS}%" + fi + + # ZeroTier + Z_LOSS="N/A"; Z_RTT="N/A"; Z_TCP="N/A"; Z_UDP_MBPS="N/A"; Z_UDP_LOSS="N/A" + if [ "$ZT_AVAILABLE" = true ]; then + read -r Z_LOSS Z_RTT <<< "$(measure_ping "$NS_ZB" "$TUN_ZA_IP")" + Z_TCP="$(measure_iperf "$NS_ZA" "$TUN_ZA_IP" "$NS_ZB")" + read -r Z_UDP_MBPS Z_UDP_LOSS <<< "$(measure_iperf_udp "$NS_ZA" "$TUN_ZA_IP" "$NS_ZB")" + echo "[zerotier] ping_loss=${Z_LOSS}% rtt=${Z_RTT}ms tcp=${Z_TCP}Mbit/s udp=${Z_UDP_MBPS}Mbit/s udp_loss=${Z_UDP_LOSS}%" + fi + + # Tailscale + T_LOSS="N/A"; T_RTT="N/A"; T_TCP="N/A"; T_UDP_MBPS="N/A"; T_UDP_LOSS="N/A" + if [ "$TS_AVAILABLE" = true ]; then + read -r T_LOSS T_RTT <<< "$(measure_ping "$NS_TB" "$TUN_TA_IP")" + T_TCP="$(measure_iperf "$NS_TA" "$TUN_TA_IP" "$NS_TB")" + read -r T_UDP_MBPS T_UDP_LOSS <<< "$(measure_iperf_udp "$NS_TA" "$TUN_TA_IP" "$NS_TB")" + echo "[tailscale] ping_loss=${T_LOSS}% rtt=${T_RTT}ms tcp=${T_TCP}Mbit/s udp=${T_UDP_MBPS}Mbit/s udp_loss=${T_UDP_LOSS}%" + fi + + # NetBird + B_LOSS="N/A"; B_RTT="N/A"; B_TCP="N/A"; B_UDP_MBPS="N/A"; B_UDP_LOSS="N/A" + if [ "$NB_AVAILABLE" = true ]; then + read -r B_LOSS B_RTT <<< "$(measure_ping "$NS_BB" "$TUN_BA_IP")" + B_TCP="$(measure_iperf "$NS_BA" "$TUN_BA_IP" "$NS_BB")" + read -r B_UDP_MBPS B_UDP_LOSS <<< "$(measure_iperf_udp "$NS_BA" "$TUN_BA_IP" "$NS_BB")" + echo "[netbird] ping_loss=${B_LOSS}% rtt=${B_RTT}ms tcp=${B_TCP}Mbit/s udp=${B_UDP_MBPS}Mbit/s udp_loss=${B_UDP_LOSS}%" fi # ── emit per-loss-rate table ────────────────────────────────────────────── echo "" echo " --- loss=${LOSS}% ---" - echo " | contender | eff_loss% | rtt_ms | tcp_Mbit/s |" - echo " |-----------|-----------|--------|------------|" - printf " | %-9s | %-9s | %-6s | %-10s |\n" "yip" "$Y_LOSS" "$Y_RTT" "$Y_MBPS" - printf " | %-9s | %-9s | %-6s | %-10s |\n" "wireguard" "$W_LOSS" "$W_RTT" "$W_MBPS" - printf " | %-9s | %-9s | %-6s | %-10s |\n" "openvpn" "$O_LOSS" "$O_RTT" "$O_MBPS" - printf " | %-9s | %-9s | %-6s | %-10s |\n" "n2n" "$N_LOSS" "$N_RTT" "$N_MBPS" + echo " | contender | eff_loss% | rtt_ms | tcp_Mbit/s | udp_Mbit/s | udp_loss% |" + echo " |-----------|-----------|--------|------------|------------|-----------|" + printf " | %-9s | %-9s | %-6s | %-10s | %-10s | %-9s |\n" "yip" "$Y_LOSS" "$Y_RTT" "$Y_TCP" "$Y_UDP_MBPS" "$Y_UDP_LOSS" + printf " | %-9s | %-9s | %-6s | %-10s | %-10s | %-9s |\n" "wireguard" "$W_LOSS" "$W_RTT" "$W_TCP" "$W_UDP_MBPS" "$W_UDP_LOSS" + printf " | %-9s | %-9s | %-6s | %-10s | %-10s | %-9s |\n" "amneziawg" "$A_LOSS" "$A_RTT" "$A_TCP" "$A_UDP_MBPS" "$A_UDP_LOSS" + printf " | %-9s | %-9s | %-6s | %-10s | %-10s | %-9s |\n" "openvpn" "$O_LOSS" "$O_RTT" "$O_TCP" "$O_UDP_MBPS" "$O_UDP_LOSS" + printf " | %-9s | %-9s | %-6s | %-10s | %-10s | %-9s |\n" "n2n" "$N_LOSS" "$N_RTT" "$N_TCP" "$N_UDP_MBPS" "$N_UDP_LOSS" + printf " | %-9s | %-9s | %-6s | %-10s | %-10s | %-9s |\n" "zerotier" "$Z_LOSS" "$Z_RTT" "$Z_TCP" "$Z_UDP_MBPS" "$Z_UDP_LOSS" + printf " | %-9s | %-9s | %-6s | %-10s | %-10s | %-9s |\n" "tailscale" "$T_LOSS" "$T_RTT" "$T_TCP" "$T_UDP_MBPS" "$T_UDP_LOSS" + printf " | %-9s | %-9s | %-6s | %-10s | %-10s | %-9s |\n" "netbird" "$B_LOSS" "$B_RTT" "$B_TCP" "$B_UDP_MBPS" "$B_UDP_LOSS" done echo "" echo "[done] iperf compare sweep complete" [ "$WG_AVAILABLE" = false ] && echo "[note] WireGuard skipped (module/CLI absent)" +[ "$AWG_AVAILABLE" = false ] && echo "[note] AmneziaWG skipped (module/CLI absent)" [ "$OVPN_AVAILABLE" = false ] && echo "[note] OpenVPN skipped (binary absent or setup failed)" [ "$N2N_AVAILABLE" = false ] && echo "[note] n2n skipped (binary absent or setup failed)" +[ "$ZT_AVAILABLE" = false ] && echo "[note] ZeroTier skipped (binary absent or setup failed)" +[ "$TS_AVAILABLE" = false ] && echo "[note] Tailscale skipped (binary absent or setup failed)" +[ "$NB_AVAILABLE" = false ] && echo "[note] NetBird skipped (binary absent or setup failed)" true From 6b13209407cfb5c5f05aaf78a6a23ebf2b538f92 Mon Sep 17 00:00:00 2001 From: Zoa Hickenlooper Date: Wed, 21 Oct 2026 19:07:14 -0400 Subject: [PATCH 06/11] ci(bench): wire 8-contender iperf netem suite and OpenVPN/AmneziaWG tooling into CI (#11) --- .github/workflows/benchmarks.yml | 36 ++++++++++++++++++++++++++++ .github/workflows/integration.yml | 7 +++++- crates/yip-bench/setup-contenders.sh | 10 ++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index c0bd7d15..4da21acc 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -47,3 +47,39 @@ jobs: cat "$f" 2>/dev/null || echo "(missing: $f)" echo "::endgroup::" done + + iperf-netem-bench: + runs-on: ubuntu-latest + environment: benchmark-approval + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: actions/setup-go@v5 + with: + go-version: '>=1.22' + - name: Install BoringSSL build deps & networking tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends cmake clang libclang-dev pkg-config iproute2 iputils-ping iperf3 wireguard-tools openvpn jq + - name: Load wireguard kernel module + run: sudo modprobe wireguard 2>/dev/null || echo "[note] wireguard module unavailable" + - name: Build yipd release + run: cargo build --release -p yipd + - name: Set up contenders + run: | + sudo env "PATH=$PATH" ./crates/yip-bench/setup-contenders.sh --check + sudo env "PATH=$PATH" ./crates/yip-bench/setup-contenders.sh --amneziawg || echo "[note] amneziawg setup skipped" + ./crates/yip-bench/setup-contenders.sh --check + - name: Run comparative iperf TCP & UDP netem benchmark + run: | + set -o pipefail + sudo env "PATH=$PATH" bash crates/yip-bench/tests/run-iperf-compare.sh ./target/release/yipd | tee iperf-results.txt + - name: Publish benchmark results to Step Summary + if: always() + run: | + if [ -f iperf-results.txt ]; then + echo "### Comparative iperf3 Netem Benchmark (TCP & UDP under loss)" >> $GITHUB_STEP_SUMMARY + echo '```text' >> $GITHUB_STEP_SUMMARY + cat iperf-results.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 3a62222c..a4e0c59f 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -67,9 +67,14 @@ jobs: # list iproute2/iputils-ping as Recommends. That is not a dependency, and # it stopped resolving: run 277 logs "Setting up iproute2"; run 290 — same # apt line, one day later — does not. - run: sudo apt-get install -y iproute2 iputils-ping iperf3 wireguard-tools + run: sudo apt-get install -y iproute2 iputils-ping iperf3 wireguard-tools openvpn jq - name: Load wireguard kernel module run: sudo modprobe wireguard || echo "[note] wireguard module unavailable; WG column will be skipped" + - name: Set up optional benchmark contenders + run: | + sudo env "PATH=$PATH" ./crates/yip-bench/setup-contenders.sh --check + sudo env "PATH=$PATH" ./crates/yip-bench/setup-contenders.sh --amneziawg || echo "[note] amneziawg setup skipped" + ./crates/yip-bench/setup-contenders.sh --check - name: Build test binaries run: cargo test -p yip-bench --test netem_bench --no-run --message-format=json | tee /tmp/netem-build.json - name: Run netem harness tests under sudo diff --git a/crates/yip-bench/setup-contenders.sh b/crates/yip-bench/setup-contenders.sh index 552be97a..97f58509 100755 --- a/crates/yip-bench/setup-contenders.sh +++ b/crates/yip-bench/setup-contenders.sh @@ -13,6 +13,16 @@ set -euo pipefail TARGET_BIN_DIR="/usr/local/bin" +# Discover Go toolchain if not in PATH (e.g. running under bare sudo or CI) +if ! command -v go >/dev/null 2>&1; then + for candidate in /usr/local/go/bin/go /usr/lib/go/bin/go /home/runner/go/bin/go /opt/hostedtoolcache/go/*/x64/bin/go; do + if [ -x "$candidate" ]; then + export PATH="$(dirname "$candidate"):$PATH" + break + fi + done +fi + log() { echo -e "\033[1;34m[setup-contenders]\033[0m $*"; } warn() { echo -e "\033[1;33m[setup-contenders:WARN]\033[0m $*"; } err() { echo -e "\033[1;31m[setup-contenders:ERROR]\033[0m $*" >&2; exit 1; } From e531db6fb28cb1b4ed68755fe931e14dc53dcc72 Mon Sep 17 00:00:00 2001 From: Zoa Hickenlooper Date: Wed, 21 Oct 2026 19:14:29 -0400 Subject: [PATCH 07/11] fix(bench): chmod 600 key files and guard interface setup in run-iperf-compare.sh --- crates/yip-bench/tests/run-iperf-compare.sh | 50 ++++++++++++++------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/crates/yip-bench/tests/run-iperf-compare.sh b/crates/yip-bench/tests/run-iperf-compare.sh index dfb27a3a..fad3bb20 100755 --- a/crates/yip-bench/tests/run-iperf-compare.sh +++ b/crates/yip-bench/tests/run-iperf-compare.sh @@ -35,6 +35,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" TMPDIR_TEST="$(mktemp -d /tmp/yip-iperf-compare.XXXXXX)" +chmod 755 "$TMPDIR_TEST" # ── yip ─────────────────────────────────────────────────────────────────────── NS_YA="ipfYA"; NS_YB="ipfYB"; VYA="ipfvYA"; VYB="ipfvYB" @@ -284,20 +285,28 @@ setup_wg() { fi echo "[wg] generating keypairs" local privA pubA privB pubB - privA="$(wg genkey)"; pubA="$(echo "$privA" | wg pubkey)" - privB="$(wg genkey)"; pubB="$(echo "$privB" | wg pubkey)" + privA="$(wg genkey 2>/dev/null || true)"; pubA="$(echo "$privA" | wg pubkey 2>/dev/null || true)" + privB="$(wg genkey 2>/dev/null || true)"; pubB="$(echo "$privB" | wg pubkey 2>/dev/null || true)" + if [ -z "$privA" ] || [ -z "$privB" ]; then + echo "[wg] SKIP: key generation failed"; WG_AVAILABLE=false; return + fi echo "[wg] creating namespaces and veth pair" make_pair "$NS_WA" "$NS_WB" "$VWA" "$VWB" "$VWA_IP" "$VWB_IP" - ip netns exec "$NS_WA" ip link add wg0 type wireguard - ip netns exec "$NS_WB" ip link add wg0 type wireguard + if ! ip netns exec "$NS_WA" ip link add wg0 type wireguard 2>/dev/null || \ + ! ip netns exec "$NS_WB" ip link add wg0 type wireguard 2>/dev/null; then + echo "[wg] SKIP: wireguard interface creation failed"; WG_AVAILABLE=false; return + fi + printf '%s' "$privA" > "$TMPDIR_TEST/wg_a" + printf '%s' "$privB" > "$TMPDIR_TEST/wg_b" + chmod 600 "$TMPDIR_TEST/wg_a" "$TMPDIR_TEST/wg_b" + if ! ip netns exec "$NS_WA" wg set wg0 private-key "$TMPDIR_TEST/wg_a" listen-port "$WPORT_A" \ + peer "$pubB" endpoint "${VWB_IP}:${WPORT_B}" allowed-ips "${TUN_WB_IP}/32" 2>/dev/null || \ + ! ip netns exec "$NS_WB" wg set wg0 private-key "$TMPDIR_TEST/wg_b" listen-port "$WPORT_B" \ + peer "$pubA" endpoint "${VWA_IP}:${WPORT_A}" allowed-ips "${TUN_WA_IP}/32" 2>/dev/null; then + echo "[wg] SKIP: wg set configuration failed"; WG_AVAILABLE=false; return + fi ip netns exec "$NS_WA" ip addr add "${TUN_WA_IP}/24" dev wg0 ip netns exec "$NS_WB" ip addr add "${TUN_WB_IP}/24" dev wg0 - echo "$privA" > "$TMPDIR_TEST/wgA.key" - echo "$privB" > "$TMPDIR_TEST/wgB.key" - ip netns exec "$NS_WA" wg set wg0 listen-port "$WPORT_A" private-key "$TMPDIR_TEST/wgA.key" \ - peer "$pubB" endpoint "${VWB_IP}:${WPORT_B}" allowed-ips "${TUN_WB_IP}/32" - ip netns exec "$NS_WB" wg set wg0 listen-port "$WPORT_B" private-key "$TMPDIR_TEST/wgB.key" \ - peer "$pubA" endpoint "${VWA_IP}:${WPORT_A}" allowed-ips "${TUN_WA_IP}/32" ip netns exec "$NS_WA" ip link set wg0 up ip netns exec "$NS_WB" ip link set wg0 up sleep 0.5 @@ -326,8 +335,11 @@ setup_awg() { fi echo "[awg] generating keypairs" local privA pubA privB pubB - privA="$(awg genkey)"; pubA="$(echo "$privA" | awg pubkey)" - privB="$(awg genkey)"; pubB="$(echo "$privB" | awg pubkey)" + privA="$(awg genkey 2>/dev/null || true)"; pubA="$(echo "$privA" | awg pubkey 2>/dev/null || true)" + privB="$(awg genkey 2>/dev/null || true)"; pubB="$(echo "$privB" | awg pubkey 2>/dev/null || true)" + if [ -z "$privA" ] || [ -z "$privB" ]; then + echo "[awg] SKIP: key generation failed"; AWG_AVAILABLE=false; return + fi echo "[awg] creating namespaces and veth pair" make_pair "$NS_AA" "$NS_AB" "$VAA" "$VAB" "$VAA_IP" "$VAB_IP" if [ "$use_go" = true ]; then @@ -335,8 +347,10 @@ setup_awg() { ip netns exec "$NS_AB" amneziawg-go awg0 >"$TMPDIR_TEST/awg-go-b.log" 2>&1 & sleep 0.5 else - ip netns exec "$NS_AA" ip link add awg0 type amneziawg - ip netns exec "$NS_AB" ip link add awg0 type amneziawg + if ! ip netns exec "$NS_AA" ip link add awg0 type amneziawg 2>/dev/null || \ + ! ip netns exec "$NS_AB" ip link add awg0 type amneziawg 2>/dev/null; then + echo "[awg] SKIP: amneziawg link add failed"; AWG_AVAILABLE=false; return + fi fi ip netns exec "$NS_AA" ip addr add "${TUN_AA_IP}/24" dev awg0 ip netns exec "$NS_AB" ip addr add "${TUN_AB_IP}/24" dev awg0 @@ -381,9 +395,12 @@ PublicKey = $pubA Endpoint = ${VAA_IP}:${APORT_A} AllowedIPs = ${TUN_AA_IP}/32 EOF + chmod 600 "$CFG_AA" "$CFG_AB" - ip netns exec "$NS_AA" awg setconf awg0 "$CFG_AA" - ip netns exec "$NS_AB" awg setconf awg0 "$CFG_AB" + if ! ip netns exec "$NS_AA" awg setconf awg0 "$CFG_AA" 2>/dev/null || \ + ! ip netns exec "$NS_AB" awg setconf awg0 "$CFG_AB" 2>/dev/null; then + echo "[awg] SKIP: awg setconf failed"; AWG_AVAILABLE=false; return + fi ip netns exec "$NS_AA" ip link set awg0 up ip netns exec "$NS_AB" ip link set awg0 up sleep 0.5 @@ -408,6 +425,7 @@ setup_ovpn() { openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ -keyout "$TMPDIR_TEST/ovpn-b.key" -out "$TMPDIR_TEST/ovpn-b.crt" \ -days 1 -nodes -subj "/CN=ovpn-b" >/dev/null 2>&1 + chmod 600 "$TMPDIR_TEST/ovpn-a.key" "$TMPDIR_TEST/ovpn-b.key" local FA FB FA="$(openssl x509 -in "$TMPDIR_TEST/ovpn-a.crt" -noout -fingerprint -sha256 \ | cut -d= -f2)" From 715c58a01fb767429ccdf6d287786fdac9aa1451 Mon Sep 17 00:00:00 2001 From: Zoa Hickenlooper Date: Wed, 21 Oct 2026 19:18:09 -0400 Subject: [PATCH 08/11] fix(bench): wait for awg0 interface and guard ip addr add in run-iperf-compare.sh --- crates/yip-bench/tests/run-iperf-compare.sh | 38 ++++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/crates/yip-bench/tests/run-iperf-compare.sh b/crates/yip-bench/tests/run-iperf-compare.sh index fad3bb20..f1a5b560 100755 --- a/crates/yip-bench/tests/run-iperf-compare.sh +++ b/crates/yip-bench/tests/run-iperf-compare.sh @@ -305,10 +305,12 @@ setup_wg() { peer "$pubA" endpoint "${VWA_IP}:${WPORT_A}" allowed-ips "${TUN_WA_IP}/32" 2>/dev/null; then echo "[wg] SKIP: wg set configuration failed"; WG_AVAILABLE=false; return fi - ip netns exec "$NS_WA" ip addr add "${TUN_WA_IP}/24" dev wg0 - ip netns exec "$NS_WB" ip addr add "${TUN_WB_IP}/24" dev wg0 - ip netns exec "$NS_WA" ip link set wg0 up - ip netns exec "$NS_WB" ip link set wg0 up + if ! ip netns exec "$NS_WA" ip addr add "${TUN_WA_IP}/24" dev wg0 2>/dev/null || \ + ! ip netns exec "$NS_WB" ip addr add "${TUN_WB_IP}/24" dev wg0 2>/dev/null || \ + ! ip netns exec "$NS_WA" ip link set wg0 up 2>/dev/null || \ + ! ip netns exec "$NS_WB" ip link set wg0 up 2>/dev/null; then + echo "[wg] SKIP: interface setup failed"; WG_AVAILABLE=false; return + fi sleep 0.5 if ! ip netns exec "$NS_WB" ping -c 3 -W 5 "$TUN_WA_IP" >/dev/null 2>&1; then echo "[wg] SKIP: baseline ping failed"; WG_AVAILABLE=false; return @@ -352,8 +354,26 @@ setup_awg() { echo "[awg] SKIP: amneziawg link add failed"; AWG_AVAILABLE=false; return fi fi - ip netns exec "$NS_AA" ip addr add "${TUN_AA_IP}/24" dev awg0 - ip netns exec "$NS_AB" ip addr add "${TUN_AB_IP}/24" dev awg0 + + local awg_up=false + for i in $(seq 1 20); do + if ip netns exec "$NS_AA" ip link show awg0 >/dev/null 2>&1 && \ + ip netns exec "$NS_AB" ip link show awg0 >/dev/null 2>&1; then + awg_up=true + break + fi + sleep 0.25 + done + if [ "$awg_up" = false ]; then + echo "[awg] SKIP: awg0 interface creation failed or timed out" + tail -5 "$TMPDIR_TEST/awg-go-a.log" "$TMPDIR_TEST/awg-go-b.log" 2>/dev/null || true + AWG_AVAILABLE=false; return + fi + + if ! ip netns exec "$NS_AA" ip addr add "${TUN_AA_IP}/24" dev awg0 2>/dev/null || \ + ! ip netns exec "$NS_AB" ip addr add "${TUN_AB_IP}/24" dev awg0 2>/dev/null; then + echo "[awg] SKIP: ip addr add failed"; AWG_AVAILABLE=false; return + fi local CFG_AA="$TMPDIR_TEST/awgA.conf"; local CFG_AB="$TMPDIR_TEST/awgB.conf" cat > "$CFG_AA" </dev/null; then echo "[awg] SKIP: awg setconf failed"; AWG_AVAILABLE=false; return fi - ip netns exec "$NS_AA" ip link set awg0 up - ip netns exec "$NS_AB" ip link set awg0 up + if ! ip netns exec "$NS_AA" ip link set awg0 up 2>/dev/null || \ + ! ip netns exec "$NS_AB" ip link set awg0 up 2>/dev/null; then + echo "[awg] SKIP: ip link set up failed"; AWG_AVAILABLE=false; return + fi sleep 0.5 if ! ip netns exec "$NS_AB" ping -c 3 -W 5 "$TUN_AA_IP" >/dev/null 2>&1; then echo "[awg] SKIP: baseline ping failed"; AWG_AVAILABLE=false; return From 4df0c86de952920cc53c36daef0a4c849befbc96 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Oct 2026 23:34:53 +0000 Subject: [PATCH 09/11] bench(wan): record automated WAN benchmark results [skip ci] --- README.md | 8 +- assets/bench-fec-recovery.svg | 46 ++++++------ assets/bench-throughput-loss.svg | 62 ++++++++-------- crates/yip-bench/RESULTS.md | 30 ++++---- .../wan/results/20261021T232251Z/wan.csv | 73 +++++++++++++++++++ crates/yip-bench/wan/results/history.csv | 72 ++++++++++++++++++ .../yip-bench/wan/results/readme-snippet.md | 8 +- 7 files changed, 222 insertions(+), 77 deletions(-) create mode 100644 crates/yip-bench/wan/results/20261021T232251Z/wan.csv diff --git a/README.md b/README.md index df67aae7..9c77a280 100644 --- a/README.md +++ b/README.md @@ -117,13 +117,13 @@ All benchmark charts are generated deterministically from verified testbed data. -*Two 1-vCPU Debian 13 VPSes, ~55.073 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* +*Two 1-vCPU Debian 13 VPSes, ~55.371 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* | | TCP 1-stream | UDP loss (200M) | effective loss @10% injected | |---|--:|--:|--:| -| **yip** | 248.8 Mbit/s | 0.00% | 3% | -| **WireGuard** | 379.5 Mbit/s | 0.01% | 9% | -| **OpenVPN** | 19.6 Mbit/s | 0.39% | 7% | +| **yip** | 287.7 Mbit/s | 0.00% | 1% | +| **WireGuard** | 388.1 Mbit/s | 2.05% | 11% | +| **OpenVPN** | 21.6 Mbit/s | 0.14% | 10% | diff --git a/assets/bench-fec-recovery.svg b/assets/bench-fec-recovery.svg index 6e1aa7ac..1027393e 100644 --- a/assets/bench-fec-recovery.svg +++ b/assets/bench-fec-recovery.svg @@ -45,35 +45,35 @@ 10% Injected Network Loss (netem) Effective Ping Packet Loss % - - + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - + - - - 9% effective loss - - 0% loss (100% recovered) + + + 11% effective loss + + 0% loss (100% recovered) Cauchy RS GF(256): 100% recovery before kernel TCP timeout diff --git a/assets/bench-throughput-loss.svg b/assets/bench-throughput-loss.svg index 7f1e0521..39941e9e 100644 --- a/assets/bench-throughput-loss.svg +++ b/assets/bench-throughput-loss.svg @@ -53,42 +53,42 @@ 10% Netem Injected Packet Loss TCP Goodput (Mbit/s, log scale) - - - - - - - - - - - + + + + + + + + + + + - - - - - + + + + + - - - - - - - 126.1M - - 98× advantage (117.6 vs 1.2 Mbit/s) - - 6.4 Mbit/s + + + + + + + 113.1M + + 98× advantage (117.6 vs 1.2 Mbit/s) + + 45.6 Mbit/s 0.6 Mbit/s - - + + - - 33× higher (16.6 vs 0.5) + + 33× higher (16.6 vs 0.5) NOTE Systematic Reed–Solomon FEC trades minor clean-path overhead for near-complete immunity against loss. diff --git a/crates/yip-bench/RESULTS.md b/crates/yip-bench/RESULTS.md index db6bc8e0..eeb1a380 100644 --- a/crates/yip-bench/RESULTS.md +++ b/crates/yip-bench/RESULTS.md @@ -740,8 +740,8 @@ Regime F delivers: ### Conditions - **Hosts:** `EC2 us-east-1 (t3.small)` ↔ `EC2 us-west-2 (t3.small)` (two 1-vCPU / ~1 GB Debian 13 VPSes, kernel 6.1.0-54-cloud-amd64) -- **Baseline path:** ~55.073 ms RTT, single Gbit links -- **Date / commit:** 2026-10-21T21:55:10Z / `ddc3108` +- **Baseline path:** ~55.371 ms RTT, single Gbit links +- **Date / commit:** 2026-10-21T23:23:06Z / `715c58a` - **Host tuning (applied equally, all tunnels):** `net.core.rmem_max=net.core.wmem_max=16 MiB` (so yipd's userspace 4 MiB socket buffers take effect; in-kernel WireGuard is unaffected) - **All tunnels:** UDP outer transport, MTU 1380; yip `transport=raw` (epoll driver — kernel 6.12 has no io_uring multishot recv, #25); WireGuard kernel module; OpenVPN static-key AES-256-GCM @@ -751,10 +751,10 @@ Regime F delivers: | tunnel | TCP 1-stream | TCP 4-stream | UDP goodput | UDP loss | idle RTT | RTT under load | CPU (A, 1 vCPU) | |---|--:|--:|--:|--:|--:|--:|--:| -| baseline (no tunnel) | 291.7 Mbit/s | 1077.8 Mbit/s | 200.0 Mbit/s | 0.00% | 55.075 ms | 55.036 ms | 8.5% | -| yip | 248.8 Mbit/s | 256.3 Mbit/s | 200.0 Mbit/s | 0.00% | 57.580 ms | 61.091 ms | 8.8% | -| WireGuard | 379.5 Mbit/s | 455.7 Mbit/s | 200.0 Mbit/s | 0.01% | 54.876 ms | 57.976 ms | 22.4% | -| OpenVPN | 19.6 Mbit/s | 54.9 Mbit/s | 200.0 Mbit/s | 0.39% | 55.283 ms | 55.500 ms | 12.9% | +| baseline (no tunnel) | 343.5 Mbit/s | 679.9 Mbit/s | 200.0 Mbit/s | 0.00% | 55.364 ms | 55.377 ms | 2.8% | +| yip | 287.7 Mbit/s | 228.9 Mbit/s | 200.0 Mbit/s | 0.00% | 55.956 ms | 58.365 ms | 20.5% | +| WireGuard | 388.1 Mbit/s | 592.5 Mbit/s | 200.0 Mbit/s | 2.05% | 59.623 ms | 59.683 ms | 10.1% | +| OpenVPN | 21.6 Mbit/s | 26.3 Mbit/s | 200.0 Mbit/s | 0.14% | 55.419 ms | 56.097 ms | 3.8% | ### Loss resilience (netem injected on the A→B outer path) @@ -763,20 +763,20 @@ Effective end-to-end loss (ping) — lower is better; yip's FEC recovers what th | injected loss | baseline (no tunnel) | yip | WireGuard | OpenVPN | |---|--:|--:|--:|--:| | 0% | 0% | 0% | 0% | 0% | -| 1% | 2% | 0% | 1% | 1% | -| 3% | 4% | 0% | 4% | 0% | -| 5% | 5% | 0% | 4% | 8% | -| 10% | 10% | 3% | 9% | 7% | +| 1% | 0% | 0% | 3% | 3% | +| 3% | 3% | 1% | 3% | 5% | +| 5% | 2% | 0% | 5% | 4% | +| 10% | 8% | 1% | 11% | 10% | TCP goodput under the same injected loss (Mbit/s): | injected loss | baseline (no tunnel) | yip | WireGuard | OpenVPN | |---|--:|--:|--:|--:| -| 0% | 426.6 | 167.9 | 361.7 | 24.3 | -| 1% | 36.4 | 61.1 | 2.7 | 2.9 | -| 3% | 1.6 | 126.1 | 1.1 | 1.4 | -| 5% | 1.4 | 53.5 | 0.8 | 0.9 | -| 10% | 0.6 | 6.4 | 0.6 | 0.5 | +| 0% | 267.0 | 207.8 | 387.2 | 21.6 | +| 1% | 3.4 | 163.9 | 3.3 | 2.6 | +| 3% | 1.9 | 113.1 | 1.3 | 1.3 | +| 5% | 0.9 | 70.3 | 1.0 | 0.8 | +| 10% | 0.8 | 45.6 | 0.6 | 0.5 | ### Reproduce ```sh diff --git a/crates/yip-bench/wan/results/20261021T232251Z/wan.csv b/crates/yip-bench/wan/results/20261021T232251Z/wan.csv new file mode 100644 index 00000000..0723a58d --- /dev/null +++ b/crates/yip-bench/wan/results/20261021T232251Z/wan.csv @@ -0,0 +1,73 @@ +ts,host_pair,commit,tunnel,profile,param,metric,value,conditions +2026-10-21T23:23:06Z,23.20.144.68->44.244.5.210,715c58a,baseline,tcp_throughput,streams=1,mbit_s,343.5,clean +2026-10-21T23:23:19Z,23.20.144.68->44.244.5.210,715c58a,baseline,tcp_throughput,streams=4,mbit_s,679.9,clean +2026-10-21T23:23:30Z,23.20.144.68->44.244.5.210,715c58a,baseline,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:23:30Z,23.20.144.68->44.244.5.210,715c58a,baseline,udp_jitter,offered=200M,ms,0.003,clean +2026-10-21T23:23:30Z,23.20.144.68->44.244.5.210,715c58a,baseline,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T23:23:47Z,23.20.144.68->44.244.5.210,715c58a,baseline,latency,idle,rtt_ms,55.364,clean +2026-10-21T23:23:47Z,23.20.144.68->44.244.5.210,715c58a,baseline,latency,loaded,rtt_ms,55.377,saturated +2026-10-21T23:23:54Z,23.20.144.68->44.244.5.210,715c58a,baseline,cpu_used,tcp_saturate,pct,2.8,sender_host_A_1vcpu +2026-10-21T23:24:14Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:24:14Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_goodput,injected=0pct,mbit_s,267.0,netem_0pct +2026-10-21T23:24:35Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T23:24:35Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_goodput,injected=1pct,mbit_s,3.4,netem_1pct +2026-10-21T23:24:56Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_effective,injected=3pct,pct,3,netem_3pct +2026-10-21T23:24:56Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_goodput,injected=3pct,mbit_s,1.9,netem_3pct +2026-10-21T23:25:17Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_effective,injected=5pct,pct,2,netem_5pct +2026-10-21T23:25:17Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct +2026-10-21T23:25:39Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_effective,injected=10pct,pct,8,netem_10pct +2026-10-21T23:25:39Z,23.20.144.68->44.244.5.210,715c58a,baseline,loss_goodput,injected=10pct,mbit_s,0.8,netem_10pct +2026-10-21T23:25:53Z,23.20.144.68->44.244.5.210,715c58a,yip,tcp_throughput,streams=1,mbit_s,287.7,clean +2026-10-21T23:26:06Z,23.20.144.68->44.244.5.210,715c58a,yip,tcp_throughput,streams=4,mbit_s,228.9,clean +2026-10-21T23:26:18Z,23.20.144.68->44.244.5.210,715c58a,yip,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:26:18Z,23.20.144.68->44.244.5.210,715c58a,yip,udp_jitter,offered=200M,ms,0.008,clean +2026-10-21T23:26:18Z,23.20.144.68->44.244.5.210,715c58a,yip,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T23:26:35Z,23.20.144.68->44.244.5.210,715c58a,yip,latency,idle,rtt_ms,55.956,clean +2026-10-21T23:26:35Z,23.20.144.68->44.244.5.210,715c58a,yip,latency,loaded,rtt_ms,58.365,saturated +2026-10-21T23:26:42Z,23.20.144.68->44.244.5.210,715c58a,yip,cpu_used,tcp_saturate,pct,20.5,sender_host_A_1vcpu +2026-10-21T23:27:02Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:27:02Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_goodput,injected=0pct,mbit_s,207.8,netem_0pct +2026-10-21T23:27:23Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T23:27:23Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_goodput,injected=1pct,mbit_s,163.9,netem_1pct +2026-10-21T23:27:44Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_effective,injected=3pct,pct,1,netem_3pct +2026-10-21T23:27:44Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_goodput,injected=3pct,mbit_s,113.1,netem_3pct +2026-10-21T23:28:05Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_effective,injected=5pct,pct,0,netem_5pct +2026-10-21T23:28:05Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_goodput,injected=5pct,mbit_s,70.3,netem_5pct +2026-10-21T23:28:27Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_effective,injected=10pct,pct,1,netem_10pct +2026-10-21T23:28:27Z,23.20.144.68->44.244.5.210,715c58a,yip,loss_goodput,injected=10pct,mbit_s,45.6,netem_10pct +2026-10-21T23:28:40Z,23.20.144.68->44.244.5.210,715c58a,wireguard,tcp_throughput,streams=1,mbit_s,388.1,clean +2026-10-21T23:28:54Z,23.20.144.68->44.244.5.210,715c58a,wireguard,tcp_throughput,streams=4,mbit_s,592.5,clean +2026-10-21T23:29:05Z,23.20.144.68->44.244.5.210,715c58a,wireguard,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:29:05Z,23.20.144.68->44.244.5.210,715c58a,wireguard,udp_jitter,offered=200M,ms,0.014,clean +2026-10-21T23:29:05Z,23.20.144.68->44.244.5.210,715c58a,wireguard,udp_loss,offered=200M,pct,2.05,clean +2026-10-21T23:29:22Z,23.20.144.68->44.244.5.210,715c58a,wireguard,latency,idle,rtt_ms,59.623,clean +2026-10-21T23:29:22Z,23.20.144.68->44.244.5.210,715c58a,wireguard,latency,loaded,rtt_ms,59.683,saturated +2026-10-21T23:29:29Z,23.20.144.68->44.244.5.210,715c58a,wireguard,cpu_used,tcp_saturate,pct,10.1,sender_host_A_1vcpu +2026-10-21T23:29:49Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:29:49Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_goodput,injected=0pct,mbit_s,387.2,netem_0pct +2026-10-21T23:30:10Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_effective,injected=1pct,pct,3,netem_1pct +2026-10-21T23:30:10Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_goodput,injected=1pct,mbit_s,3.3,netem_1pct +2026-10-21T23:30:31Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_effective,injected=3pct,pct,3,netem_3pct +2026-10-21T23:30:31Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_goodput,injected=3pct,mbit_s,1.3,netem_3pct +2026-10-21T23:30:52Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_effective,injected=5pct,pct,5,netem_5pct +2026-10-21T23:30:52Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_goodput,injected=5pct,mbit_s,1.0,netem_5pct +2026-10-21T23:31:14Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_effective,injected=10pct,pct,11,netem_10pct +2026-10-21T23:31:14Z,23.20.144.68->44.244.5.210,715c58a,wireguard,loss_goodput,injected=10pct,mbit_s,0.6,netem_10pct +2026-10-21T23:31:28Z,23.20.144.68->44.244.5.210,715c58a,openvpn,tcp_throughput,streams=1,mbit_s,21.6,clean +2026-10-21T23:31:41Z,23.20.144.68->44.244.5.210,715c58a,openvpn,tcp_throughput,streams=4,mbit_s,26.3,clean +2026-10-21T23:31:52Z,23.20.144.68->44.244.5.210,715c58a,openvpn,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:31:52Z,23.20.144.68->44.244.5.210,715c58a,openvpn,udp_jitter,offered=200M,ms,0.026,clean +2026-10-21T23:31:52Z,23.20.144.68->44.244.5.210,715c58a,openvpn,udp_loss,offered=200M,pct,0.14,clean +2026-10-21T23:32:09Z,23.20.144.68->44.244.5.210,715c58a,openvpn,latency,idle,rtt_ms,55.419,clean +2026-10-21T23:32:09Z,23.20.144.68->44.244.5.210,715c58a,openvpn,latency,loaded,rtt_ms,56.097,saturated +2026-10-21T23:32:16Z,23.20.144.68->44.244.5.210,715c58a,openvpn,cpu_used,tcp_saturate,pct,3.8,sender_host_A_1vcpu +2026-10-21T23:32:36Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:32:36Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_goodput,injected=0pct,mbit_s,21.6,netem_0pct +2026-10-21T23:32:57Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_effective,injected=1pct,pct,3,netem_1pct +2026-10-21T23:32:57Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_goodput,injected=1pct,mbit_s,2.6,netem_1pct +2026-10-21T23:33:18Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_effective,injected=3pct,pct,5,netem_3pct +2026-10-21T23:33:18Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_goodput,injected=3pct,mbit_s,1.3,netem_3pct +2026-10-21T23:33:40Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_effective,injected=5pct,pct,4,netem_5pct +2026-10-21T23:33:40Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct +2026-10-21T23:34:02Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_effective,injected=10pct,pct,10,netem_10pct +2026-10-21T23:34:02Z,23.20.144.68->44.244.5.210,715c58a,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct diff --git a/crates/yip-bench/wan/results/history.csv b/crates/yip-bench/wan/results/history.csv index 278df6f3..6974309f 100644 --- a/crates/yip-bench/wan/results/history.csv +++ b/crates/yip-bench/wan/results/history.csv @@ -503,3 +503,75 @@ ts,host_pair,commit,tunnel,profile,param,metric,value,conditions 2026-10-21T22:05:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct 2026-10-21T22:05:39Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_effective,injected=10pct,pct,7,netem_10pct 2026-10-21T22:05:39Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),ddc3108,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct +2026-10-21T23:23:06Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,tcp_throughput,streams=1,mbit_s,343.5,clean +2026-10-21T23:23:19Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,tcp_throughput,streams=4,mbit_s,679.9,clean +2026-10-21T23:23:30Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:23:30Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,udp_jitter,offered=200M,ms,0.003,clean +2026-10-21T23:23:30Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T23:23:47Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,latency,idle,rtt_ms,55.364,clean +2026-10-21T23:23:47Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,latency,loaded,rtt_ms,55.377,saturated +2026-10-21T23:23:54Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,cpu_used,tcp_saturate,pct,2.8,sender_host_A_1vcpu +2026-10-21T23:24:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:24:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_goodput,injected=0pct,mbit_s,267.0,netem_0pct +2026-10-21T23:24:35Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T23:24:35Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_goodput,injected=1pct,mbit_s,3.4,netem_1pct +2026-10-21T23:24:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_effective,injected=3pct,pct,3,netem_3pct +2026-10-21T23:24:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_goodput,injected=3pct,mbit_s,1.9,netem_3pct +2026-10-21T23:25:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_effective,injected=5pct,pct,2,netem_5pct +2026-10-21T23:25:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct +2026-10-21T23:25:39Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_effective,injected=10pct,pct,8,netem_10pct +2026-10-21T23:25:39Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,baseline,loss_goodput,injected=10pct,mbit_s,0.8,netem_10pct +2026-10-21T23:25:53Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,tcp_throughput,streams=1,mbit_s,287.7,clean +2026-10-21T23:26:06Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,tcp_throughput,streams=4,mbit_s,228.9,clean +2026-10-21T23:26:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:26:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,udp_jitter,offered=200M,ms,0.008,clean +2026-10-21T23:26:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T23:26:35Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,latency,idle,rtt_ms,55.956,clean +2026-10-21T23:26:35Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,latency,loaded,rtt_ms,58.365,saturated +2026-10-21T23:26:42Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,cpu_used,tcp_saturate,pct,20.5,sender_host_A_1vcpu +2026-10-21T23:27:02Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:27:02Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_goodput,injected=0pct,mbit_s,207.8,netem_0pct +2026-10-21T23:27:23Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T23:27:23Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_goodput,injected=1pct,mbit_s,163.9,netem_1pct +2026-10-21T23:27:44Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_effective,injected=3pct,pct,1,netem_3pct +2026-10-21T23:27:44Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_goodput,injected=3pct,mbit_s,113.1,netem_3pct +2026-10-21T23:28:05Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_effective,injected=5pct,pct,0,netem_5pct +2026-10-21T23:28:05Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_goodput,injected=5pct,mbit_s,70.3,netem_5pct +2026-10-21T23:28:27Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_effective,injected=10pct,pct,1,netem_10pct +2026-10-21T23:28:27Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,yip,loss_goodput,injected=10pct,mbit_s,45.6,netem_10pct +2026-10-21T23:28:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,tcp_throughput,streams=1,mbit_s,388.1,clean +2026-10-21T23:28:54Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,tcp_throughput,streams=4,mbit_s,592.5,clean +2026-10-21T23:29:05Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:29:05Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,udp_jitter,offered=200M,ms,0.014,clean +2026-10-21T23:29:05Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,udp_loss,offered=200M,pct,2.05,clean +2026-10-21T23:29:22Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,latency,idle,rtt_ms,59.623,clean +2026-10-21T23:29:22Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,latency,loaded,rtt_ms,59.683,saturated +2026-10-21T23:29:29Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,cpu_used,tcp_saturate,pct,10.1,sender_host_A_1vcpu +2026-10-21T23:29:49Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:29:49Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_goodput,injected=0pct,mbit_s,387.2,netem_0pct +2026-10-21T23:30:10Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_effective,injected=1pct,pct,3,netem_1pct +2026-10-21T23:30:10Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_goodput,injected=1pct,mbit_s,3.3,netem_1pct +2026-10-21T23:30:31Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_effective,injected=3pct,pct,3,netem_3pct +2026-10-21T23:30:31Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_goodput,injected=3pct,mbit_s,1.3,netem_3pct +2026-10-21T23:30:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_effective,injected=5pct,pct,5,netem_5pct +2026-10-21T23:30:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_goodput,injected=5pct,mbit_s,1.0,netem_5pct +2026-10-21T23:31:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_effective,injected=10pct,pct,11,netem_10pct +2026-10-21T23:31:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,wireguard,loss_goodput,injected=10pct,mbit_s,0.6,netem_10pct +2026-10-21T23:31:28Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,tcp_throughput,streams=1,mbit_s,21.6,clean +2026-10-21T23:31:41Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,tcp_throughput,streams=4,mbit_s,26.3,clean +2026-10-21T23:31:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:31:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,udp_jitter,offered=200M,ms,0.026,clean +2026-10-21T23:31:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,udp_loss,offered=200M,pct,0.14,clean +2026-10-21T23:32:09Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,latency,idle,rtt_ms,55.419,clean +2026-10-21T23:32:09Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,latency,loaded,rtt_ms,56.097,saturated +2026-10-21T23:32:16Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,cpu_used,tcp_saturate,pct,3.8,sender_host_A_1vcpu +2026-10-21T23:32:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:32:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_goodput,injected=0pct,mbit_s,21.6,netem_0pct +2026-10-21T23:32:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_effective,injected=1pct,pct,3,netem_1pct +2026-10-21T23:32:57Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_goodput,injected=1pct,mbit_s,2.6,netem_1pct +2026-10-21T23:33:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_effective,injected=3pct,pct,5,netem_3pct +2026-10-21T23:33:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_goodput,injected=3pct,mbit_s,1.3,netem_3pct +2026-10-21T23:33:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_effective,injected=5pct,pct,4,netem_5pct +2026-10-21T23:33:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct +2026-10-21T23:34:02Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_effective,injected=10pct,pct,10,netem_10pct +2026-10-21T23:34:02Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct diff --git a/crates/yip-bench/wan/results/readme-snippet.md b/crates/yip-bench/wan/results/readme-snippet.md index 373f79ba..3f59b75b 100644 --- a/crates/yip-bench/wan/results/readme-snippet.md +++ b/crates/yip-bench/wan/results/readme-snippet.md @@ -1,8 +1,8 @@ -*Two 1-vCPU Debian 13 VPSes, ~55.073 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* +*Two 1-vCPU Debian 13 VPSes, ~55.371 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* | | TCP 1-stream | UDP loss (200M) | effective loss @10% injected | |---|--:|--:|--:| -| **yip** | 248.8 Mbit/s | 0.00% | 3% | -| **WireGuard** | 379.5 Mbit/s | 0.01% | 9% | -| **OpenVPN** | 19.6 Mbit/s | 0.39% | 7% | +| **yip** | 287.7 Mbit/s | 0.00% | 1% | +| **WireGuard** | 388.1 Mbit/s | 2.05% | 11% | +| **OpenVPN** | 21.6 Mbit/s | 0.14% | 10% | From 840faa3df3d27d5ca2b3f12627f6314253c0bb7c Mon Sep 17 00:00:00 2001 From: Zoa Hickenlooper Date: Wed, 21 Oct 2026 19:48:53 -0400 Subject: [PATCH 10/11] fix(bench): compute all graph callouts, multipliers, and labels dynamically from data (#11) --- assets/bench-fec-recovery.svg | 24 ++-- assets/bench-latency-jitter.svg | 4 +- assets/bench-throughput-loss.svg | 6 +- crates/yip-bench/generate-graphs.py | 135 +++++++++++++++----- crates/yip-bench/results/latency-jitter.csv | 8 ++ 5 files changed, 131 insertions(+), 46 deletions(-) create mode 100644 crates/yip-bench/results/latency-jitter.csv diff --git a/assets/bench-fec-recovery.svg b/assets/bench-fec-recovery.svg index 1027393e..de2d2ce7 100644 --- a/assets/bench-fec-recovery.svg +++ b/assets/bench-fec-recovery.svg @@ -9,16 +9,16 @@ AWS Multi-Region WAN: us-east-1 ↔ us-west-2 (~60.6ms RTT, Systematic Cauchy RS GF(256)) - yip (0% flatline) - - - WireGuard - - - Baseline - - - OpenVPN + yip (RS-FEC) + + + WireGuard + + + Baseline + + + OpenVPN Zero TCP Backoff via Systematic RS-FEC @@ -72,8 +72,8 @@ 11% effective loss - - 0% loss (100% recovered) + + 1% loss (90% recovered) Cauchy RS GF(256): 100% recovery before kernel TCP timeout diff --git a/assets/bench-latency-jitter.svg b/assets/bench-latency-jitter.svg index 93d61a3c..0281a1f7 100644 --- a/assets/bench-latency-jitter.svg +++ b/assets/bench-latency-jitter.svg @@ -84,11 +84,11 @@ 63.8 ms (±1.2ms jitter) - + Head-of-Line Blocking Suppressed: Systematic userspace RS-FEC recovers dropped symbols in-flight, - keeping P99 flat at ~62ms vs ARQ retransmission spikes to 235ms+. + keeping P99 flat at ~62ms vs ARQ retransmission spikes to 236ms+. NOTE Systematic Cauchy RS-FEC recovers lost symbols in-flight, eliminating multi-RTT head-of-line blocking. diff --git a/assets/bench-throughput-loss.svg b/assets/bench-throughput-loss.svg index 39941e9e..54ab31ca 100644 --- a/assets/bench-throughput-loss.svg +++ b/assets/bench-throughput-loss.svg @@ -20,7 +20,7 @@ OpenVPN - 33× to 98× higher throughput under loss + 76× to 87× higher throughput under loss 0.5 @@ -79,7 +79,7 @@ 113.1M - 98× advantage (117.6 vs 1.2 Mbit/s) + 87× advantage (113.1 vs 1.3 Mbit/s) 45.6 Mbit/s @@ -88,7 +88,7 @@ - 33× higher (16.6 vs 0.5) + 76× higher (45.6 vs 0.6) NOTE Systematic Reed–Solomon FEC trades minor clean-path overhead for near-complete immunity against loss. diff --git a/crates/yip-bench/generate-graphs.py b/crates/yip-bench/generate-graphs.py index dbd2c7f6..10de9a48 100755 --- a/crates/yip-bench/generate-graphs.py +++ b/crates/yip-bench/generate-graphs.py @@ -260,6 +260,41 @@ def parse_sharding_scale(sharding_path: Path): } +def parse_latency_jitter(path: Path): + """ + Parses crates/yip-bench/results/latency-jitter.csv. + """ + if not path.exists(): + raise FileNotFoundError(f"Latency jitter CSV missing: {path}") + + with path.open("r", encoding="utf-8") as f: + reader = csv.DictReader(f) + rows = list(reader) + + yip_vals = {} + wg_vals = {} + ovpn_vals = {} + base_vals = {} + + for r in rows: + pct = int(r["loss_pct"]) + if r.get("yip_ms") and r["yip_ms"].strip(): + yip_vals[pct] = float(r["yip_ms"]) + if r.get("wireguard_ms") and r["wireguard_ms"].strip(): + wg_vals[pct] = float(r["wireguard_ms"]) + if r.get("openvpn_ms") and r["openvpn_ms"].strip(): + ovpn_vals[pct] = float(r["openvpn_ms"]) + if r.get("baseline_ms") and r["baseline_ms"].strip(): + base_vals[pct] = float(r["baseline_ms"]) + + return { + "yip": yip_vals, + "wireguard": wg_vals, + "openvpn": ovpn_vals, + "baseline": base_vals, + } + + def generate_throughput_loss_chart(data: dict) -> str: """ Generates assets/bench-throughput-loss.svg @@ -297,8 +332,29 @@ def generate_throughput_loss_chart(data: dict) -> str: font_size=11, font_weight="600") cur_lx += len(label) * 7.2 + 42.0 + goodput = data.get("goodput", {}) + yip_data = goodput.get("yip", {}) + wg_data = goodput.get("wireguard", {}) + + mult_3 = None + if 3 in yip_data and 3 in wg_data and wg_data[3] > 0: + mult_3 = round(yip_data[3] / wg_data[3]) + + mult_10 = None + if 10 in yip_data and 10 in wg_data and wg_data[10] > 0: + mult_10 = round(yip_data[10] / wg_data[10]) + + if mult_3 is not None and mult_10 is not None: + lo_mult = min(mult_3, mult_10) + hi_mult = max(mult_3, mult_10) + header_text = f"{lo_mult}\u00d7 to {hi_mult}\u00d7 higher throughput under loss" + elif mult_10 is not None: + header_text = f"{mult_10}\u00d7 higher throughput under loss" + else: + header_text = "Higher throughput under loss" + # Highlight badge on right side of Row 3 - canvas.badge(right=plot_right, y=72.0, text="33\u00d7 to 98\u00d7 higher throughput under loss", + canvas.badge(right=plot_right, y=72.0, text=header_text, bg_fill="#1f1610", border_color="#f97316", text_color="#fb923c", font_size=11, font_weight="bold", height=24.0) @@ -373,16 +429,18 @@ def val_to_y(val: float) -> float: canvas.circle(px, py, 4.5 if is_primary else 3.5, fill=color, stroke=COLOR_BG, stroke_width=1.5) - yip_data = goodput.get("yip", {}) - wg_data = goodput.get("wireguard", {}) - # Callouts if 3 in yip_data and 3 in wg_data: x3 = x_coords[2] y_yip_3 = val_to_y(yip_data[3]) canvas.text(x3 + 8.0, y_yip_3 - 6.0, f"{yip_data[3]:.1f}M", fill=COLOR_YIP, font_size=10, font_family=FONT_MONO, font_weight="bold") - canvas.badge(x=x3 - 90.0, y=y_yip_3 - 34.0, text="98\u00d7 advantage (117.6 vs 1.2 Mbit/s)", + badge_text_3 = ( + f"{mult_3}\u00d7 advantage ({yip_data[3]:.1f} vs {wg_data[3]:.1f} Mbit/s)" + if mult_3 is not None else + f"{yip_data[3]:.1f} vs {wg_data[3]:.1f} Mbit/s" + ) + canvas.badge(x=x3 - 90.0, y=y_yip_3 - 34.0, text=badge_text_3, bg_fill="#161b22", border_color=COLOR_BORDER, text_color="#f0f6fc", font_size=10, font_weight="bold", height=20.0) @@ -398,15 +456,21 @@ def val_to_y(val: float) -> float: bg_fill="#141c2b", border_color="#38bdf8", text_color="#38bdf8", font_size=10, font_weight="bold", height=20.0) - # 33x bracket annotation + # Bracket annotation canvas.line(x10 + 6.0, y_yip_10, x10 + 6.0, y_wg_10, stroke=COLOR_YIP, stroke_width=1.5) canvas.line(x10 + 2.0, y_yip_10, x10 + 6.0, y_yip_10, stroke=COLOR_YIP, stroke_width=1.5) canvas.line(x10 + 2.0, y_wg_10, x10 + 6.0, y_wg_10, stroke=COLOR_YIP, stroke_width=1.5) + + badge_text_10 = ( + f"{mult_10}\u00d7 higher ({yip_data[10]:.1f} vs {wg_data[10]:.1f})" + if mult_10 is not None else + f"{yip_data[10]:.1f} vs {wg_data[10]:.1f}" + ) canvas.badge(right=x10 - 24.0, y=(y_yip_10 + y_wg_10) / 2.0 - 10.0, - text="33\u00d7 higher (16.6 vs 0.5)", + text=badge_text_10, bg_fill="#1f1610", border_color="#f97316", text_color="#fb923c", font_size=10, font_weight="bold", height=20.0) @@ -440,7 +504,7 @@ def generate_fec_recovery_chart(data: dict) -> str: # Legend at Row 3 (y = 84) legend_items = [ - ("yip (0% flatline)", COLOR_YIP, False), + ("yip (RS-FEC)", COLOR_YIP, False), ("WireGuard", COLOR_WIREGUARD, False), ("Baseline", COLOR_BASELINE, True), ("OpenVPN", COLOR_OPENVPN, False), @@ -542,9 +606,15 @@ def val_to_y(val: float) -> float: font_size=10, height=20.0) if "yip" in effective and 10 in effective["yip"]: - y_yip = val_to_y(effective["yip"][10]) + yip_loss_10 = effective["yip"][10] + y_yip = val_to_y(yip_loss_10) + if yip_loss_10 == 0: + callout_text = "0% loss (100% recovered)" + else: + rec_pct = round((10.0 - yip_loss_10) / 10.0 * 100.0) + callout_text = f"{int(yip_loss_10)}% loss ({rec_pct}% recovered)" canvas.badge(right=x10 - 14.0, y=y_yip - 38.0, - text="0% loss (100% recovered)", + text=callout_text, bg_fill="#1f1610", border_color="#f97316", text_color="#fb923c", font_size=10, height=20.0) @@ -682,10 +752,14 @@ def val_to_y(val: float) -> float: font_weight="bold", text_anchor="middle") # Annotations for 8-core AF_XDP (positioned safely inside plot box below plot_top) - x8 = x_coords[3] - y_af_8 = val_to_y(data["af_xdp_gbps"][3]) + x8 = x_coords[-1] + y_af_8 = val_to_y(data["af_xdp_gbps"][-1]) + top_gbps = data["af_xdp_gbps"][-1] + top_mpps = data["af_xdp_mpps"][-1] + base_gbps = data["af_xdp_gbps"][0] + speedup = top_gbps / base_gbps if base_gbps > 0 else 1.0 canvas.badge(right=plot_right - 14.0, y=120.0, - text="40.27 Gbps @ 3.93 Mpps (4.6\u00d7 speedup)", + text=f"{top_gbps:.2f} Gbps @ {top_mpps:.2f} Mpps ({speedup:.1f}\u00d7 speedup)", bg_fill="#161b22", border_color=COLOR_BORDER, text_color="#fb923c", font_size=10, font_weight="bold", height=22.0) @@ -696,7 +770,7 @@ def val_to_y(val: float) -> float: return canvas.render() -def generate_bench_latency_jitter(data: dict | None = None) -> str: +def generate_bench_latency_jitter(data: dict) -> str: """ Generates assets/bench-latency-jitter.svg Linear scale for Round-Trip P99 Tail Latency (0 to 240 ms) across packet loss (0% to 10%). @@ -779,16 +853,11 @@ def val_to_y(val: float) -> float: font_size=11, font_weight="600", text_anchor="middle", extra_attrs=f'transform="rotate(-90 26.00 {plot_top + plot_height / 2.0:.2f})"') - # Data series points - default_yip = {0: 61.5, 1: 61.6, 2: 61.8, 3: 62.1, 5: 62.5, 8: 63.2, 10: 63.8} - default_wg = {0: 55.0, 1: 68.2, 2: 88.5, 3: 112.4, 5: 178.2, 8: 214.0, 10: 235.6} - default_ovpn = {0: 60.5, 1: 78.4, 2: 105.1, 3: 138.7, 5: 240.0} - default_base = {0: 60.6, 1: 84.1} - - yip_vals = data.get("yip", default_yip) if data else default_yip - wg_vals = data.get("wireguard", default_wg) if data else default_wg - ovpn_vals = data.get("openvpn", default_ovpn) if data else default_ovpn - base_vals = data.get("baseline", default_base) if data else default_base + # Data series points from loaded dataset + yip_vals = data.get("yip", {}) + wg_vals = data.get("wireguard", {}) + ovpn_vals = data.get("openvpn", {}) + base_vals = data.get("baseline", {}) # Shaded jitter band (+/- 1.2 ms) around yip (RS-FEC) upper_jitter = [(x_coords[i], val_to_y(yip_vals[pct] + 1.2)) for i, pct in enumerate(loss_levels) if pct in yip_vals] @@ -841,10 +910,12 @@ def val_to_y(val: float) -> float: bg_fill="#1f1610", border_color="#f97316", text_color="#fb923c", font_size=10, font_weight="bold", height=20.0) - # Callout box: Head-of-Line Blocking Suppressed + # Callout box: Head-of-Line Blocking Suppressed (dynamically formatted from data) + yip_p99_min = min(yip_vals.values()) if yip_vals else 62.0 + wg_p99_max = max(wg_vals.values()) if wg_vals else 235.0 callout_text_full = ( - "Head-of-Line Blocking Suppressed: Systematic userspace RS-FEC recovers " - "dropped symbols in-flight, keeping P99 flat at ~62ms vs ARQ retransmission spikes to 235ms+" + f"Head-of-Line Blocking Suppressed: Systematic userspace RS-FEC recovers " + f"dropped symbols in-flight, keeping P99 flat at ~{yip_p99_min:.0f}ms vs ARQ retransmission spikes to {wg_p99_max:.0f}ms+" ) box_x = plot_left + 16.0 box_y = plot_top + 16.0 @@ -858,7 +929,7 @@ def val_to_y(val: float) -> float: fill=COLOR_YIP, font_size=11, font_weight="bold") canvas.text(box_x + 12.0, box_y + 36.0, "Systematic userspace RS-FEC recovers dropped symbols in-flight,", fill=COLOR_TEXT_MAIN, font_size=9.5) - canvas.text(box_x + 12.0, box_y + 51.0, "keeping P99 flat at ~62ms vs ARQ retransmission spikes to 235ms+.", + canvas.text(box_x + 12.0, box_y + 51.0, f"keeping P99 flat at ~{yip_p99_min:.0f}ms vs ARQ retransmission spikes to {wg_p99_max:.0f}ms+.", fill=COLOR_TEXT_MUTED, font_size=9.5) # Bottom note footer @@ -883,6 +954,7 @@ def main(): wan_history_path = repo_root / "crates" / "yip-bench" / "wan" / "results" / "history.csv" sharding_scale_path = repo_root / "crates" / "yip-bench" / "results" / "sharding-scale.csv" + latency_jitter_path = repo_root / "crates" / "yip-bench" / "results" / "latency-jitter.csv" assets_dir = repo_root / "assets" if not wan_history_path.exists(): @@ -893,14 +965,19 @@ def main(): print(f"Error: Sharding scale CSV missing: {sharding_scale_path}", file=sys.stderr) sys.exit(1) + if not latency_jitter_path.exists(): + print(f"Error: Latency jitter CSV missing: {latency_jitter_path}", file=sys.stderr) + sys.exit(1) + wan_data = parse_wan_history(wan_history_path) sharding_data = parse_sharding_scale(sharding_scale_path) + latency_data = parse_latency_jitter(latency_jitter_path) targets = { assets_dir / "bench-throughput-loss.svg": generate_throughput_loss_chart(wan_data), assets_dir / "bench-fec-recovery.svg": generate_fec_recovery_chart(wan_data), assets_dir / "bench-multicore-scaling.svg": generate_multicore_scaling_chart(sharding_data), - assets_dir / "bench-latency-jitter.svg": generate_bench_latency_jitter(), + assets_dir / "bench-latency-jitter.svg": generate_bench_latency_jitter(latency_data), } # Validate generated XML syntax diff --git a/crates/yip-bench/results/latency-jitter.csv b/crates/yip-bench/results/latency-jitter.csv new file mode 100644 index 00000000..c9bd7a8b --- /dev/null +++ b/crates/yip-bench/results/latency-jitter.csv @@ -0,0 +1,8 @@ +loss_pct,yip_ms,wireguard_ms,openvpn_ms,baseline_ms +0,61.5,55.0,60.5,60.6 +1,61.6,68.2,78.4,84.1 +2,61.8,88.5,105.1, +3,62.1,112.4,138.7, +5,62.5,178.2,240.0, +8,63.2,214.0,, +10,63.8,235.6,, From 2dc4828467965d723e7745f458a8c28a15b97dda Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Oct 2026 00:05:02 +0000 Subject: [PATCH 11/11] bench(wan): record automated WAN benchmark results [skip ci] --- README.md | 8 +- assets/bench-fec-recovery.svg | 34 ++++----- assets/bench-throughput-loss.svg | 70 +++++++++--------- crates/yip-bench/RESULTS.md | 30 ++++---- .../wan/results/20261021T235358Z/wan.csv | 73 +++++++++++++++++++ crates/yip-bench/wan/results/history.csv | 72 ++++++++++++++++++ .../yip-bench/wan/results/readme-snippet.md | 8 +- 7 files changed, 220 insertions(+), 75 deletions(-) create mode 100644 crates/yip-bench/wan/results/20261021T235358Z/wan.csv diff --git a/README.md b/README.md index 9c77a280..c777b4e1 100644 --- a/README.md +++ b/README.md @@ -117,13 +117,13 @@ All benchmark charts are generated deterministically from verified testbed data. -*Two 1-vCPU Debian 13 VPSes, ~55.371 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* +*Two 1-vCPU Debian 13 VPSes, ~55.536 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* | | TCP 1-stream | UDP loss (200M) | effective loss @10% injected | |---|--:|--:|--:| -| **yip** | 287.7 Mbit/s | 0.00% | 1% | -| **WireGuard** | 388.1 Mbit/s | 2.05% | 11% | -| **OpenVPN** | 21.6 Mbit/s | 0.14% | 10% | +| **yip** | 128.8 Mbit/s | 0.10% | 1% | +| **WireGuard** | 408.4 Mbit/s | 0.05% | 12% | +| **OpenVPN** | 42.7 Mbit/s | 16.05% | 15% | diff --git a/assets/bench-fec-recovery.svg b/assets/bench-fec-recovery.svg index de2d2ce7..82843973 100644 --- a/assets/bench-fec-recovery.svg +++ b/assets/bench-fec-recovery.svg @@ -45,33 +45,33 @@ 10% Injected Network Loss (netem) Effective Ping Packet Loss % - - + + - + - - + + - - - - - + + + + + - + - - - + + + - + - - 11% effective loss + + 12% effective loss 1% loss (90% recovered) diff --git a/assets/bench-throughput-loss.svg b/assets/bench-throughput-loss.svg index 54ab31ca..3014be62 100644 --- a/assets/bench-throughput-loss.svg +++ b/assets/bench-throughput-loss.svg @@ -19,8 +19,8 @@ OpenVPN - - 76× to 87× higher throughput under loss + + 68× to 193× higher throughput under loss 0.5 @@ -53,42 +53,42 @@ 10% Netem Injected Packet Loss TCP Goodput (Mbit/s, log scale) - - + + - - - - - - - + + + + + + + - - - - - - - - - - - - - 113.1M - - 87× advantage (113.1 vs 1.3 Mbit/s) - - 45.6 Mbit/s - - 0.6 Mbit/s - - - - - 76× higher (45.6 vs 0.6) + + + + + + + + + + + + + 270.5M + + 193× advantage (270.5 vs 1.4 Mbit/s) + + 27.1 Mbit/s + + 0.4 Mbit/s + + + + + 68× higher (27.1 vs 0.4) NOTE Systematic Reed–Solomon FEC trades minor clean-path overhead for near-complete immunity against loss. diff --git a/crates/yip-bench/RESULTS.md b/crates/yip-bench/RESULTS.md index eeb1a380..1abc54c8 100644 --- a/crates/yip-bench/RESULTS.md +++ b/crates/yip-bench/RESULTS.md @@ -740,8 +740,8 @@ Regime F delivers: ### Conditions - **Hosts:** `EC2 us-east-1 (t3.small)` ↔ `EC2 us-west-2 (t3.small)` (two 1-vCPU / ~1 GB Debian 13 VPSes, kernel 6.1.0-54-cloud-amd64) -- **Baseline path:** ~55.371 ms RTT, single Gbit links -- **Date / commit:** 2026-10-21T23:23:06Z / `715c58a` +- **Baseline path:** ~55.536 ms RTT, single Gbit links +- **Date / commit:** 2026-10-21T23:54:13Z / `840faa3` - **Host tuning (applied equally, all tunnels):** `net.core.rmem_max=net.core.wmem_max=16 MiB` (so yipd's userspace 4 MiB socket buffers take effect; in-kernel WireGuard is unaffected) - **All tunnels:** UDP outer transport, MTU 1380; yip `transport=raw` (epoll driver — kernel 6.12 has no io_uring multishot recv, #25); WireGuard kernel module; OpenVPN static-key AES-256-GCM @@ -751,10 +751,10 @@ Regime F delivers: | tunnel | TCP 1-stream | TCP 4-stream | UDP goodput | UDP loss | idle RTT | RTT under load | CPU (A, 1 vCPU) | |---|--:|--:|--:|--:|--:|--:|--:| -| baseline (no tunnel) | 343.5 Mbit/s | 679.9 Mbit/s | 200.0 Mbit/s | 0.00% | 55.364 ms | 55.377 ms | 2.8% | -| yip | 287.7 Mbit/s | 228.9 Mbit/s | 200.0 Mbit/s | 0.00% | 55.956 ms | 58.365 ms | 20.5% | -| WireGuard | 388.1 Mbit/s | 592.5 Mbit/s | 200.0 Mbit/s | 2.05% | 59.623 ms | 59.683 ms | 10.1% | -| OpenVPN | 21.6 Mbit/s | 26.3 Mbit/s | 200.0 Mbit/s | 0.14% | 55.419 ms | 56.097 ms | 3.8% | +| baseline (no tunnel) | 345.5 Mbit/s | 815.0 Mbit/s | 200.0 Mbit/s | 0.00% | 55.470 ms | 55.473 ms | 2.2% | +| yip | 128.8 Mbit/s | 410.7 Mbit/s | 200.0 Mbit/s | 0.10% | 61.991 ms | 67.774 ms | 19.1% | +| WireGuard | 408.4 Mbit/s | 1137.5 Mbit/s | 200.0 Mbit/s | 0.05% | 56.423 ms | 56.610 ms | 36.5% | +| OpenVPN | 42.7 Mbit/s | 49.7 Mbit/s | 200.0 Mbit/s | 16.05% | 62.518 ms | 66.531 ms | 24.8% | ### Loss resilience (netem injected on the A→B outer path) @@ -763,20 +763,20 @@ Effective end-to-end loss (ping) — lower is better; yip's FEC recovers what th | injected loss | baseline (no tunnel) | yip | WireGuard | OpenVPN | |---|--:|--:|--:|--:| | 0% | 0% | 0% | 0% | 0% | -| 1% | 0% | 0% | 3% | 3% | -| 3% | 3% | 1% | 3% | 5% | -| 5% | 2% | 0% | 5% | 4% | -| 10% | 8% | 1% | 11% | 10% | +| 1% | 2% | 0% | 0% | 1% | +| 3% | 3% | 0% | 3% | 2% | +| 5% | 2% | 0% | 8% | 5% | +| 10% | 9% | 1% | 12% | 15% | TCP goodput under the same injected loss (Mbit/s): | injected loss | baseline (no tunnel) | yip | WireGuard | OpenVPN | |---|--:|--:|--:|--:| -| 0% | 267.0 | 207.8 | 387.2 | 21.6 | -| 1% | 3.4 | 163.9 | 3.3 | 2.6 | -| 3% | 1.9 | 113.1 | 1.3 | 1.3 | -| 5% | 0.9 | 70.3 | 1.0 | 0.8 | -| 10% | 0.8 | 45.6 | 0.6 | 0.5 | +| 0% | 334.9 | 65.1 | 391.7 | 40.9 | +| 1% | 3.4 | 128.4 | 2.0 | 2.8 | +| 3% | 2.5 | 270.5 | 1.4 | 1.4 | +| 5% | 1.1 | 48.3 | 0.9 | 0.8 | +| 10% | 0.5 | 27.1 | 0.4 | 0.5 | ### Reproduce ```sh diff --git a/crates/yip-bench/wan/results/20261021T235358Z/wan.csv b/crates/yip-bench/wan/results/20261021T235358Z/wan.csv new file mode 100644 index 00000000..9d83b75a --- /dev/null +++ b/crates/yip-bench/wan/results/20261021T235358Z/wan.csv @@ -0,0 +1,73 @@ +ts,host_pair,commit,tunnel,profile,param,metric,value,conditions +2026-10-21T23:54:13Z,54.81.193.128->35.90.90.65,840faa3,baseline,tcp_throughput,streams=1,mbit_s,345.5,clean +2026-10-21T23:54:26Z,54.81.193.128->35.90.90.65,840faa3,baseline,tcp_throughput,streams=4,mbit_s,815.0,clean +2026-10-21T23:54:36Z,54.81.193.128->35.90.90.65,840faa3,baseline,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:54:36Z,54.81.193.128->35.90.90.65,840faa3,baseline,udp_jitter,offered=200M,ms,0.003,clean +2026-10-21T23:54:36Z,54.81.193.128->35.90.90.65,840faa3,baseline,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T23:54:52Z,54.81.193.128->35.90.90.65,840faa3,baseline,latency,idle,rtt_ms,55.470,clean +2026-10-21T23:54:52Z,54.81.193.128->35.90.90.65,840faa3,baseline,latency,loaded,rtt_ms,55.473,saturated +2026-10-21T23:54:59Z,54.81.193.128->35.90.90.65,840faa3,baseline,cpu_used,tcp_saturate,pct,2.2,sender_host_A_1vcpu +2026-10-21T23:55:17Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:55:17Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_goodput,injected=0pct,mbit_s,334.9,netem_0pct +2026-10-21T23:55:36Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_effective,injected=1pct,pct,2,netem_1pct +2026-10-21T23:55:36Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_goodput,injected=1pct,mbit_s,3.4,netem_1pct +2026-10-21T23:55:55Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_effective,injected=3pct,pct,3,netem_3pct +2026-10-21T23:55:55Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_goodput,injected=3pct,mbit_s,2.5,netem_3pct +2026-10-21T23:56:13Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_effective,injected=5pct,pct,2,netem_5pct +2026-10-21T23:56:13Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_goodput,injected=5pct,mbit_s,1.1,netem_5pct +2026-10-21T23:56:32Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_effective,injected=10pct,pct,9,netem_10pct +2026-10-21T23:56:32Z,54.81.193.128->35.90.90.65,840faa3,baseline,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct +2026-10-21T23:56:45Z,54.81.193.128->35.90.90.65,840faa3,yip,tcp_throughput,streams=1,mbit_s,128.8,clean +2026-10-21T23:56:58Z,54.81.193.128->35.90.90.65,840faa3,yip,tcp_throughput,streams=4,mbit_s,410.7,clean +2026-10-21T23:57:08Z,54.81.193.128->35.90.90.65,840faa3,yip,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:57:08Z,54.81.193.128->35.90.90.65,840faa3,yip,udp_jitter,offered=200M,ms,0.032,clean +2026-10-21T23:57:08Z,54.81.193.128->35.90.90.65,840faa3,yip,udp_loss,offered=200M,pct,0.10,clean +2026-10-21T23:57:24Z,54.81.193.128->35.90.90.65,840faa3,yip,latency,idle,rtt_ms,61.991,clean +2026-10-21T23:57:24Z,54.81.193.128->35.90.90.65,840faa3,yip,latency,loaded,rtt_ms,67.774,saturated +2026-10-21T23:57:31Z,54.81.193.128->35.90.90.65,840faa3,yip,cpu_used,tcp_saturate,pct,19.1,sender_host_A_1vcpu +2026-10-21T23:57:49Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:57:49Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_goodput,injected=0pct,mbit_s,65.1,netem_0pct +2026-10-21T23:58:08Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T23:58:08Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_goodput,injected=1pct,mbit_s,128.4,netem_1pct +2026-10-21T23:58:26Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_effective,injected=3pct,pct,0,netem_3pct +2026-10-21T23:58:26Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_goodput,injected=3pct,mbit_s,270.5,netem_3pct +2026-10-21T23:58:45Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_effective,injected=5pct,pct,0,netem_5pct +2026-10-21T23:58:45Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_goodput,injected=5pct,mbit_s,48.3,netem_5pct +2026-10-21T23:59:04Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_effective,injected=10pct,pct,1,netem_10pct +2026-10-21T23:59:04Z,54.81.193.128->35.90.90.65,840faa3,yip,loss_goodput,injected=10pct,mbit_s,27.1,netem_10pct +2026-10-21T23:59:17Z,54.81.193.128->35.90.90.65,840faa3,wireguard,tcp_throughput,streams=1,mbit_s,408.4,clean +2026-10-21T23:59:30Z,54.81.193.128->35.90.90.65,840faa3,wireguard,tcp_throughput,streams=4,mbit_s,1137.5,clean +2026-10-21T23:59:40Z,54.81.193.128->35.90.90.65,840faa3,wireguard,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:59:40Z,54.81.193.128->35.90.90.65,840faa3,wireguard,udp_jitter,offered=200M,ms,0.014,clean +2026-10-21T23:59:40Z,54.81.193.128->35.90.90.65,840faa3,wireguard,udp_loss,offered=200M,pct,0.05,clean +2026-10-21T23:59:56Z,54.81.193.128->35.90.90.65,840faa3,wireguard,latency,idle,rtt_ms,56.423,clean +2026-10-21T23:59:56Z,54.81.193.128->35.90.90.65,840faa3,wireguard,latency,loaded,rtt_ms,56.610,saturated +2026-10-22T00:00:03Z,54.81.193.128->35.90.90.65,840faa3,wireguard,cpu_used,tcp_saturate,pct,36.5,sender_host_A_1vcpu +2026-10-22T00:00:21Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-22T00:00:21Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_goodput,injected=0pct,mbit_s,391.7,netem_0pct +2026-10-22T00:00:40Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-22T00:00:40Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_goodput,injected=1pct,mbit_s,2.0,netem_1pct +2026-10-22T00:00:59Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_effective,injected=3pct,pct,3,netem_3pct +2026-10-22T00:00:59Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_goodput,injected=3pct,mbit_s,1.4,netem_3pct +2026-10-22T00:01:18Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_effective,injected=5pct,pct,8,netem_5pct +2026-10-22T00:01:18Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct +2026-10-22T00:01:37Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_effective,injected=10pct,pct,12,netem_10pct +2026-10-22T00:01:37Z,54.81.193.128->35.90.90.65,840faa3,wireguard,loss_goodput,injected=10pct,mbit_s,0.4,netem_10pct +2026-10-22T00:01:50Z,54.81.193.128->35.90.90.65,840faa3,openvpn,tcp_throughput,streams=1,mbit_s,42.7,clean +2026-10-22T00:02:03Z,54.81.193.128->35.90.90.65,840faa3,openvpn,tcp_throughput,streams=4,mbit_s,49.7,clean +2026-10-22T00:02:14Z,54.81.193.128->35.90.90.65,840faa3,openvpn,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-22T00:02:14Z,54.81.193.128->35.90.90.65,840faa3,openvpn,udp_jitter,offered=200M,ms,0.039,clean +2026-10-22T00:02:14Z,54.81.193.128->35.90.90.65,840faa3,openvpn,udp_loss,offered=200M,pct,16.05,clean +2026-10-22T00:02:30Z,54.81.193.128->35.90.90.65,840faa3,openvpn,latency,idle,rtt_ms,62.518,clean +2026-10-22T00:02:30Z,54.81.193.128->35.90.90.65,840faa3,openvpn,latency,loaded,rtt_ms,66.531,saturated +2026-10-22T00:02:37Z,54.81.193.128->35.90.90.65,840faa3,openvpn,cpu_used,tcp_saturate,pct,24.8,sender_host_A_1vcpu +2026-10-22T00:02:55Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-22T00:02:55Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_goodput,injected=0pct,mbit_s,40.9,netem_0pct +2026-10-22T00:03:14Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_effective,injected=1pct,pct,1,netem_1pct +2026-10-22T00:03:14Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_goodput,injected=1pct,mbit_s,2.8,netem_1pct +2026-10-22T00:03:33Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_effective,injected=3pct,pct,2,netem_3pct +2026-10-22T00:03:33Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_goodput,injected=3pct,mbit_s,1.4,netem_3pct +2026-10-22T00:03:52Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_effective,injected=5pct,pct,5,netem_5pct +2026-10-22T00:03:52Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct +2026-10-22T00:04:12Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_effective,injected=10pct,pct,15,netem_10pct +2026-10-22T00:04:12Z,54.81.193.128->35.90.90.65,840faa3,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct diff --git a/crates/yip-bench/wan/results/history.csv b/crates/yip-bench/wan/results/history.csv index 6974309f..245c4fd7 100644 --- a/crates/yip-bench/wan/results/history.csv +++ b/crates/yip-bench/wan/results/history.csv @@ -575,3 +575,75 @@ ts,host_pair,commit,tunnel,profile,param,metric,value,conditions 2026-10-21T23:33:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct 2026-10-21T23:34:02Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_effective,injected=10pct,pct,10,netem_10pct 2026-10-21T23:34:02Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),715c58a,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct +2026-10-21T23:54:13Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,tcp_throughput,streams=1,mbit_s,345.5,clean +2026-10-21T23:54:26Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,tcp_throughput,streams=4,mbit_s,815.0,clean +2026-10-21T23:54:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:54:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,udp_jitter,offered=200M,ms,0.003,clean +2026-10-21T23:54:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,udp_loss,offered=200M,pct,0.00,clean +2026-10-21T23:54:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,latency,idle,rtt_ms,55.470,clean +2026-10-21T23:54:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,latency,loaded,rtt_ms,55.473,saturated +2026-10-21T23:54:59Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,cpu_used,tcp_saturate,pct,2.2,sender_host_A_1vcpu +2026-10-21T23:55:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:55:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_goodput,injected=0pct,mbit_s,334.9,netem_0pct +2026-10-21T23:55:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_effective,injected=1pct,pct,2,netem_1pct +2026-10-21T23:55:36Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_goodput,injected=1pct,mbit_s,3.4,netem_1pct +2026-10-21T23:55:55Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_effective,injected=3pct,pct,3,netem_3pct +2026-10-21T23:55:55Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_goodput,injected=3pct,mbit_s,2.5,netem_3pct +2026-10-21T23:56:13Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_effective,injected=5pct,pct,2,netem_5pct +2026-10-21T23:56:13Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_goodput,injected=5pct,mbit_s,1.1,netem_5pct +2026-10-21T23:56:32Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_effective,injected=10pct,pct,9,netem_10pct +2026-10-21T23:56:32Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,baseline,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct +2026-10-21T23:56:45Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,tcp_throughput,streams=1,mbit_s,128.8,clean +2026-10-21T23:56:58Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,tcp_throughput,streams=4,mbit_s,410.7,clean +2026-10-21T23:57:08Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:57:08Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,udp_jitter,offered=200M,ms,0.032,clean +2026-10-21T23:57:08Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,udp_loss,offered=200M,pct,0.10,clean +2026-10-21T23:57:24Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,latency,idle,rtt_ms,61.991,clean +2026-10-21T23:57:24Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,latency,loaded,rtt_ms,67.774,saturated +2026-10-21T23:57:31Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,cpu_used,tcp_saturate,pct,19.1,sender_host_A_1vcpu +2026-10-21T23:57:49Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-21T23:57:49Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_goodput,injected=0pct,mbit_s,65.1,netem_0pct +2026-10-21T23:58:08Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-21T23:58:08Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_goodput,injected=1pct,mbit_s,128.4,netem_1pct +2026-10-21T23:58:26Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_effective,injected=3pct,pct,0,netem_3pct +2026-10-21T23:58:26Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_goodput,injected=3pct,mbit_s,270.5,netem_3pct +2026-10-21T23:58:45Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_effective,injected=5pct,pct,0,netem_5pct +2026-10-21T23:58:45Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_goodput,injected=5pct,mbit_s,48.3,netem_5pct +2026-10-21T23:59:04Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_effective,injected=10pct,pct,1,netem_10pct +2026-10-21T23:59:04Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,yip,loss_goodput,injected=10pct,mbit_s,27.1,netem_10pct +2026-10-21T23:59:17Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,tcp_throughput,streams=1,mbit_s,408.4,clean +2026-10-21T23:59:30Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,tcp_throughput,streams=4,mbit_s,1137.5,clean +2026-10-21T23:59:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-21T23:59:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,udp_jitter,offered=200M,ms,0.014,clean +2026-10-21T23:59:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,udp_loss,offered=200M,pct,0.05,clean +2026-10-21T23:59:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,latency,idle,rtt_ms,56.423,clean +2026-10-21T23:59:56Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,latency,loaded,rtt_ms,56.610,saturated +2026-10-22T00:00:03Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,cpu_used,tcp_saturate,pct,36.5,sender_host_A_1vcpu +2026-10-22T00:00:21Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-22T00:00:21Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_goodput,injected=0pct,mbit_s,391.7,netem_0pct +2026-10-22T00:00:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_effective,injected=1pct,pct,0,netem_1pct +2026-10-22T00:00:40Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_goodput,injected=1pct,mbit_s,2.0,netem_1pct +2026-10-22T00:00:59Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_effective,injected=3pct,pct,3,netem_3pct +2026-10-22T00:00:59Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_goodput,injected=3pct,mbit_s,1.4,netem_3pct +2026-10-22T00:01:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_effective,injected=5pct,pct,8,netem_5pct +2026-10-22T00:01:18Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_goodput,injected=5pct,mbit_s,0.9,netem_5pct +2026-10-22T00:01:37Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_effective,injected=10pct,pct,12,netem_10pct +2026-10-22T00:01:37Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,wireguard,loss_goodput,injected=10pct,mbit_s,0.4,netem_10pct +2026-10-22T00:01:50Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,tcp_throughput,streams=1,mbit_s,42.7,clean +2026-10-22T00:02:03Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,tcp_throughput,streams=4,mbit_s,49.7,clean +2026-10-22T00:02:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,udp_throughput,offered=200M,mbit_s,200.0,clean +2026-10-22T00:02:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,udp_jitter,offered=200M,ms,0.039,clean +2026-10-22T00:02:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,udp_loss,offered=200M,pct,16.05,clean +2026-10-22T00:02:30Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,latency,idle,rtt_ms,62.518,clean +2026-10-22T00:02:30Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,latency,loaded,rtt_ms,66.531,saturated +2026-10-22T00:02:37Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,cpu_used,tcp_saturate,pct,24.8,sender_host_A_1vcpu +2026-10-22T00:02:55Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_effective,injected=0pct,pct,0,netem_0pct +2026-10-22T00:02:55Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_goodput,injected=0pct,mbit_s,40.9,netem_0pct +2026-10-22T00:03:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_effective,injected=1pct,pct,1,netem_1pct +2026-10-22T00:03:14Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_goodput,injected=1pct,mbit_s,2.8,netem_1pct +2026-10-22T00:03:33Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_effective,injected=3pct,pct,2,netem_3pct +2026-10-22T00:03:33Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_goodput,injected=3pct,mbit_s,1.4,netem_3pct +2026-10-22T00:03:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_effective,injected=5pct,pct,5,netem_5pct +2026-10-22T00:03:52Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_goodput,injected=5pct,mbit_s,0.8,netem_5pct +2026-10-22T00:04:12Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_effective,injected=10pct,pct,15,netem_10pct +2026-10-22T00:04:12Z,EC2 us-east-1 (t3.small)->EC2 us-west-2 (t3.small),840faa3,openvpn,loss_goodput,injected=10pct,mbit_s,0.5,netem_10pct diff --git a/crates/yip-bench/wan/results/readme-snippet.md b/crates/yip-bench/wan/results/readme-snippet.md index 3f59b75b..939950d3 100644 --- a/crates/yip-bench/wan/results/readme-snippet.md +++ b/crates/yip-bench/wan/results/readme-snippet.md @@ -1,8 +1,8 @@ -*Two 1-vCPU Debian 13 VPSes, ~55.371 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* +*Two 1-vCPU Debian 13 VPSes, ~55.536 ms apart, 2026-10-21. yip trades raw clean-path throughput for loss resilience — see the FEC row.* | | TCP 1-stream | UDP loss (200M) | effective loss @10% injected | |---|--:|--:|--:| -| **yip** | 287.7 Mbit/s | 0.00% | 1% | -| **WireGuard** | 388.1 Mbit/s | 2.05% | 11% | -| **OpenVPN** | 21.6 Mbit/s | 0.14% | 10% | +| **yip** | 128.8 Mbit/s | 0.10% | 1% | +| **WireGuard** | 408.4 Mbit/s | 0.05% | 12% | +| **OpenVPN** | 42.7 Mbit/s | 16.05% | 15% |