A reliable, ordered, congestion-controlled transport protocol built on top of unreliable UDP — written in idiomatic, high-performance asynchronous Rust.
AeroUDP behaves like a stripped-down hybrid of TCP and QUIC: a real three-way handshake, cumulative ACKs, RFC 6298 RTO estimation, NewReno-style congestion control, fast retransmit, sliding-window flow control, graceful FIN exchange, keepalives, and a tokio-native API.
The full wire format and state machine are documented in docs/PROTOCOL.md.
crates/
aeroudp/ core library
src/
packet/ binary header, flags, checksum
serialization/ zero-copy encode/decode
transport/ sliding window, sequence helpers
retransmission/ in-flight queue + RTO timers
congestion/ AIMD controller + RFC6298 RTT estimator
connection/ state machine, handshake, driver glue
socket/ tokio UDP driver, listener, endpoint
simulator/ configurable lossy network
metrics/ atomic per-connection counters
aeroudp-cli/ server, client, analyzer/proxy
docs/
PROTOCOL.md wire format and state machine
examples/
file_transfer.rs end-to-end file transfer demo
cargo build --release
cargo test
cargo bench --bench packet_codec
Echo server on 127.0.0.1:9000:
cargo run --release -p aeroudp-cli --bin aeroudp-server -- \
--listen 127.0.0.1:9000 echo
Client that pings the echo server:
cargo run --release -p aeroudp-cli --bin aeroudp-client -- \
--server 127.0.0.1:9000 echo --text "hello aero" --count 3
File transfer:
# Receiver
cargo run --release -p aeroudp-cli --bin aeroudp-server -- \
--listen 127.0.0.1:9000 recv --out /tmp/received.bin
# Sender
cargo run --release -p aeroudp-cli --bin aeroudp-client -- \
--server 127.0.0.1:9000 send --file /etc/hosts
Run with a lossy network in front (analyzer in proxy mode):
cargo run --release -p aeroudp-cli --bin aeroudp-analyzer -- proxy \
--listen 127.0.0.1:9500 \
--upstream 127.0.0.1:9000 \
--loss 0.05 --reorder 0.03 \
--min-latency-ms 20 --max-latency-ms 60 --jitter-ms 10
Point the client at the proxy:
cargo run --release -p aeroudp-cli --bin aeroudp-client -- \
--server 127.0.0.1:9500 flood --bytes 16777216
use aeroudp::{AeroListener, AeroSocket, Config};
use bytes::Bytes;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let listener = AeroListener::bind("127.0.0.1:0".parse()?).await?;
let addr = listener.local_addr()?;
tokio::spawn(async move {
let conn = listener.accept().await.unwrap();
let data = conn.recv().await.unwrap();
conn.send(data).await.unwrap();
});
let conn = AeroSocket::connect("0.0.0.0:0".parse()?, addr).await?;
conn.send(Bytes::from_static(b"hi")).await?;
let echoed = conn.recv().await?;
assert_eq!(&echoed[..], b"hi");
Ok(())
}Every Connection exposes a ConnectionMetrics handle backed by atomics:
let snap = conn.metrics().snapshot();
println!("{snap}");Outputs counters for packets sent/received, bytes, retransmissions, duplicate ACKs, out-of-order packets, checksum failures, smoothed RTT, current RTO, cwnd and ssthresh, in-flight count, and derived loss rate.
cargo test— unit, integration, proptest reliability testscargo test --test stress— multi-megabyte loopback transferscargo bench— criterion microbenchmarks for codec and end-to-end throughput
Dual-licensed under MIT or Apache 2.0.