A QUIC server that reflects three times the traffic it receives at a spoofed address is not a transport bug — it’s a DDoS amplifier with your name on it.
Today we released OxiQUIC 0.2.1 — a security-hardening release for the COOLJAPAN Pure Rust QUIC transport and HTTP/3 stack. It closes five distinct RFC 9000 gaps that let an attacker turn a well-behaved QUIC endpoint into a reflection amplifier or a memory-exhaustion target, adds full bidirectional ECN support, and gives multipath connections real per-path congestion control instead of one shared average.
No ring. No aws-lc-rs. No FFI, no -sys crates on the path you actually ship. OxiQUIC builds its RFC 9000/9001/9002 stack directly on the rustls::quic TLS 1.3 API, driven by an in-house Pure Rust crypto provider over tokio UDP — compiles to a single static binary with no system libraries and no build-time C toolchain in the way.
Why OxiQUIC 0.2.1 is a game changer
Five separate cracks in the RFC 9000 security model, each independently exploitable, all fixed in the same release:
- A server responding to an unvalidated client address had no cap on how much it would send before that address proved it could receive — a classic reflection-amplification setup, and the default posture, not an edge case.
- STREAM, RESET_STREAM, and MAX_STREAM_DATA frames referencing a stream ID past the advertised concurrency limit were accepted, letting a peer grow the per-stream map without bound.
- CRYPTO frame reassembly buffered pre-handshake, unauthenticated data with no size limit — a gift to anyone who wants to spend your memory before the handshake even completes.
- An address adopted mid-connection during migration inherited the original handshake’s address validation instead of getting its own allowance — one injected packet from a spoofed source turned an already-established connection back into a reflector.
- A forged Retry packet was undetectable, because the integrity tag is keyed by a value sent in the clear. Retry — the mechanism that’s supposed to prove address ownership — provided none of its own guarantee.
OxiQUIC 0.2.1 ends all of that.
- Anti-amplification is now enforced everywhere an unvalidated address exists — the original handshake path and every path a connection migrates to, each with its own independent three-times-received allowance (RFC 9000 §8.1, §9.3).
- Stream limits, CRYPTO buffering, and flow control are all bounded and validated — STREAM_LIMIT_ERROR, CRYPTO_BUFFER_EXCEEDED, and FLOW_CONTROL_ERROR now fire exactly where RFC 9000 says they should, closing three independent unbounded-growth vectors.
- Retry’s connection-ID transcript is authenticated end-to-end — both endpoints now verify
original_destination_connection_id,retry_source_connection_id, andinitial_source_connection_idagainst what was actually observed on the wire, closing the connection on any mismatch. - Full bidirectional ECN (RFC 9000 §13.4 / RFC 9002 §7.4) — egress marking and CE-triggered congestion response, plus receive-side codepoint extraction straight off the socket (
IP_RECVTOS/IPV6_RECVTCLASS) on Linux, macOS, iOS, Android, and FreeBSD. - Real per-path congestion control for multipath — every path now owns its own RFC 9002 RTT estimator and congestion controller, so
LowestRtt/HighestBandwidth/RoundRobinscheduling ranks paths by their own measurements instead of one shared connection-level average. - 445 tests passing (
--all-features; 440 default), zero clippy warnings, zerounwrap()/panic!in production code, across ~24,000 SLOC in 5 crates.
One thing to know before you upgrade: RFC 9000 §7.3 requires both endpoints to send initial_source_connection_id, and 0.2.1 now correctly rejects a peer that omits it. OxiQUIC ≤ 0.2.0 never sent that transport parameter, so 0.2.1 cannot complete a handshake with an OxiQUIC ≤ 0.2.0 peer — both sides need to upgrade together. Interop with other RFC 9000 implementations is unaffected; they always sent it.
Technical Deep Dive: where the hardening lives
- Anti-amplification and path validation (
oxiquic-transport::connection,endpoint). Per-path allowance tracking now lives alongside the existing handshake-path accounting, and a new path-validation timer (RFC 9000 §8.2.1/§8.2.3/§8.2.4) replaces an unanswered PATH_CHALLENGE with a fresh-nonce one on PTO expiry, doubling the interval per attempt and abandoning validation cleanly after three tries. - Retry authentication (
Connection::new_server_after_retry,RetryTranscript). The full §7.3 connection-ID transcript is captured and verified, and the bundled server endpoint wires it in automatically — no opt-in required. - ECN, both directions (
oxiquic-transport::ecn,endpoint::ecn_recv).Connection::handle_datagram_with_metatakes aDatagramMetacarrying the datagram’s source address and IP ECN codepoint; it’s counted per RFC 9000 §13.4.1 and echoed back as an ACK-ECN (0x03) frame. On an unsupported platform or a kernel that refuses the socket option, the reason is typed (EcnRecvUnsupported::Platform/::SockOpt) — nothing is synthesized, and a missing codepoint is never silently counted as Not-ECT. - Per-path recovery (
multipath::PathRecovery). Every sent packet now records the path it went out on, so acks, losses, and CE marks attribute correctly — the piece multipath scheduling needed to rank paths by their own RTT and bandwidth instead of a connection-wide blend. - Runnable examples (
oxiquic/examples).quic_echo_server/quic_echo_client(a self-signed loopback echo pair,--features dangerous) andh3_get(a self-contained HTTP/3 GET round trip,--features h3) mirror the README quick-start snippets and are compiled bycargo build --examples.
Getting Started
[dependencies]
oxiquic = "0.2.1"
Open a QUIC connection and a bidirectional stream:
use oxiquic::prelude::*;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr: SocketAddr = "93.184.216.34:443".parse()?;
let conn = oxiquic::connect(addr, "example.com").await?;
let (stream_id, mut send, mut recv) = conn.open_bidi().await?;
// ... write/read via AsyncWrite / AsyncRead
Ok(())
}
Or run the new echo example directly:
cargo run --example quic_echo_server -p oxiquic --features dangerous
cargo run --example quic_echo_client -p oxiquic --features dangerous
What’s New in 0.2.1
- Security: anti-amplification for unvalidated addresses on both the handshake path and every migration path (RFC 9000 §8.1/§9.3); STREAM_LIMIT_ERROR enforcement on out-of-range stream IDs; bounded CRYPTO reassembly (CRYPTO_BUFFER_EXCEEDED); authenticated Retry connection-ID transcript (RFC 9000 §7.3).
- Fixed: stream/connection flow control now checked against MAX_STREAM_DATA/MAX_DATA; RESET_STREAM
final_sizevalidated against flow control and prior state; BBR min-RTT derived from the correct RFC 9002latest_rttsample; packet-number decode uses the correct half-window boundary; connection-ID issuance now bounded by the peer’sactive_connection_id_limitinstead of our own; MTU probes no longer steal an unvalidated address’s amplification allowance; repeated path-validation restarts no longer reset the PTO backoff on every packet. - Added: bidirectional ECN (egress marking + ingress socket extraction on Linux/macOS/iOS/Android/FreeBSD); per-path congestion control and RTT estimation for multipath;
Connection::new_server_after_retryandRetryTranscript; a path-validation timer with exponential backoff and clean abandonment; connection-ID rotation on migration (RFC 9000 §5.1.1/§9.5);cargo-fuzztargets for the three attacker-facing wire parsers (peek_dcid,packet_decode,frame_decode); runnablequic_echo_server/quic_echo_client/h3_getexamples. - Changed:
oxicrypto0.2.0 → 0.3.0,oxitls/oxitls-core/oxitls-rcgen0.2.0 → 0.3.0;aead0.5.2 → 0.6.1,aes-gcm0.10.3 → 0.11.0,chacha20poly13050.10.1 → 0.11.0; MSRV raised 1.80 → 1.85;endpoint/mod.rssplit (demux loop moved toendpoint/demux.rs) to stay under the 2000-line file cap.
Tips
- Upgrade both endpoints together. The
initial_source_connection_idenforcement in this release is a hard interop break against OxiQUIC ≤ 0.2.0 — check both sides of any connection you control before rolling out. - If you rely on connection migration, the new path-validation timer changes failure behavior. An unanswered PATH_CHALLENGE is now replaced (fresh nonce, per §8.2.1) rather than left to hang; after three PTO-scaled attempts the candidate path is marked
PathValidation::Failedand the connection falls back to its already-validated address — callpath_validation_failed()/path_challenge_attempts()if you want visibility into that state machine. - Turn on ECN reporting with
reports_inbound_ecn(). Both endpoint types expose it, plusQuicConnection::ecn_state()/ecn_recv_counts()— useful for confirming your deployment actually gets ECN-marked congestion signals rather than falling back to loss-only detection. - Multipath users:
LowestRtt/HighestBandwidth/RoundRobinnow rank on real per-path data. If you were compensating for the old shared-average behavior with your own path selection logic, it’s worth re-benchmarking against the built-in schedulers. - Fuzz your own integrations against the new targets.
cargo +nightly fuzz run peek_dcid|packet_decode|frame_decodefromcrates/oxiquic-transport/fuzzexercises exactly the attacker-facing parsing surface a hostile peer or on-path observer can reach. dangerousstays dev-only.features = ["dangerous"]unlocksconnect_insecure()for the new echo examples — keep it out of anything that ships.
This is the foundation
OxiQUIC belongs to NoFFI — the COOLJAPAN initiative to replace every C/C++/Fortran/-sys FFI dependency in the Rust ecosystem with a clean, memory-safe, 100% Pure Rust implementation. A security release that closes reflection-amplification and memory-exhaustion vectors is exactly the kind of hardening that keeps a “safe by construction” transport credible under real attack traffic, not just under a fuzzer.
OxiQUIC sits underneath a growing list of sibling COOLJAPAN projects: OxiHTTP and OxiRPC carry it as their HTTP/3 and QUIC transport layer, and MielinOS uses it for its networking stack. The crypto provider stands on OxiCrypto, and the optional TLS provider plugs into OxiTLS, keeping the entire handshake path Pure Rust from the AEAD up to the certificate chain.
Repository: https://github.com/cool-japan/oxiquic
Star the repo if you want a QUIC stack that closes reflection-amplification vectors instead of shipping them by default. ⭐
The era of “well, ring handles the crypto so it’s probably fine” is over. Pure Rust QUIC — hardened, safe, and sovereign — is here.
— KitaSan at COOLJAPAN OÜ August 6, 2026