udp-relay-core
Lock-free, cache-line-aligned building blocks for high-performance UDP relay and tunnel servers.
Why this crate?
If you're building a UDP relay, game server proxy, VPN tunnel, or any service that forwards packets between many clients at high throughput, you need per-client state that is:
- Fast — no mutexes on the hot path; bare atomics only.
- Concurrent — safely shared across threads without contention.
- Observable — bandwidth, latency, packet loss, and priority scores available at a glance.
udp-relay-core gives you exactly that in a small, dependency-light package. You bring your own I/O layer (tokio, mio, io-uring, etc.) and plug these types in.
Use cases
- Game server relays — track per-player bandwidth and timeout idle clients.
- VPN / tunnel endpoints — monitor connection quality and prioritize traffic.
- Media streaming proxies — detect slow or lossy paths and react in real time.
- Load balancers — score backends by measured latency and loss, not just round-robin.
What's inside
| Type / Function | What it does |
|---|---|
TunnelClient |
Per-client state: timeout tracking, bandwidth estimation (EWMA), latency, packet loss, lazy priority scoring. Cache-line-aligned with hot/cold field separation. |
QualityAnalyzer |
Lightweight packet loss tracker with automatic counter halving to prevent overflow. |
validate_address() |
Rejects loopback, unspecified, broadcast, multicast, and port-0 addresses. |
create_dashmap_with_capacity() |
Creates a DashMap with a shard count tuned to your CPU count. |
Getting started
Add to your Cargo.toml:
[]
= "0.1"
Track clients
use Arc;
use ;
let clients = ;
let addr = "1.2.3.4:5678".parse.unwrap;
let client = new;
clients.insert;
// On every received packet
let now = current_timestamp;
client.update_stats;
client.set_last_receive_tick_at;
// Periodic cleanup
if client.is_timed_out
Monitor bandwidth and priority
use TunnelClient;
let client = new;
// Feed traffic over time
let t0 = current_timestamp;
client.update_stats;
client.update_stats;
// Report quality metrics from your ping/probe logic
client.set_latency; // 25 ms
client.set_packet_loss_rate; // 5.0%
// Priority is recomputed lazily — only when metrics change
let _score = client.get_priority; // lower = better
Track packet loss
use QualityAnalyzer;
let qa = new;
qa.record_packet; // received OK
qa.record_packet; // lost
// Returns 0–1000 (i.e. 0.0%–100.0%)
let _rate = qa.get_packet_loss_rate;
Fast timestamps for event loops
For the lowest-overhead timestamp access, call update_clock() once per event-loop tick and use recent_timestamp() everywhere else:
use TunnelClient;
// Once per tick
update_clock;
// Zero-overhead read (single atomic load)
let _now = recent_timestamp;
Performance notes
- All shared state uses bare atomics — no
Mutex, noRwLock. TunnelClientisSend + Syncand designed to sit behindArcin aDashMap.- Hot-path fields (packet receive, bandwidth) are grouped on the first cache line; cold fields (priority, connection age) on the second — minimizing false sharing.
- Timestamps use
coarsetime(CLOCK_MONOTONIC_COARSEon Linux), which is 10–25x faster thanSystemTime::now(). - Bandwidth estimation uses an exponentially weighted moving average (EWMA) so the value adapts quickly without storing a history buffer.