proxy-header
A lightweight Rust library for parsing and encoding HAProxy PROXY protocol v1 and v2 headers, with optional async Tokio stream support.
Repository Health
Technical Analysis
proxy-header is a focused Rust crate that implements the HAProxy PROXY protocol — the de facto mechanism load balancers and proxies use to forward the original client address alongside a proxied TCP or UDP connection. It covers the full specification for both the human-readable v1 text format and the compact binary v2 format, including TLV (type-length-value) fields for extras like TLS details, unique connection IDs, and authority (SNI) strings.
Beyond raw parsing and encoding, the crate ships an io module with ProxiedStream, a wrapper that reads and strips the PROXY header from an underlying stream before handing the caller a clean byte stream — implemented for both std::io::Read/Write and, behind an optional tokio feature flag, Tokio’s async I/O traits. This makes it a practical building block for any Rust proxy, load balancer, or backend service that needs to recover real client IPs when sitting behind HAProxy, AWS ELB/ALB, or similar PROXY-protocol-aware infrastructure.
What You Get
- A
ProxyHeader::parsedecoder that reads PROXY protocol v1 or v2 headers directly from a byte buffer and returns the parsed header plus the number of bytes consumed - Encoders (
encode_v1,encode_v2, and slice/writer variants) for producing valid PROXY protocol headers when your service is the one initiating the proxied connection - Full TLV support via the
Tlvsiterator andTlvenum, covering unique connection IDs, authority/SNI strings, and SSL/TLS metadata (client cert flags, TLS version, verification result) - A
ProxiedStreamwrapper in theiomodule that transparently reads and strips the header from a live stream, implemented for both blockingstd::ioand async Tokio I/O - Typed
ProxiedAddressandProtocol(Stream/Datagram) structs so consuming code works with realSocketAddrvalues instead of manually parsed strings
Common Use Cases
- Recovering the real client IP/port in a Rust-based reverse proxy, load balancer, or edge service sitting behind HAProxy, AWS ELB, or ALB
- Building a custom TCP/UDP proxy that needs to emit valid PROXY protocol v1 or v2 headers toward an upstream service
- Wrapping a Tokio
TcpStreamso downstream connection-handling code sees a clean stream with the proxy header already stripped and its address available viaproxy_header() - Inspecting TLS termination details (client cert presence, negotiated TLS version, SNI) forwarded by an upstream proxy via PROXY protocol v2 TLVs
Under The Hood
Architecture
The crate is organized as a small set of single-purpose modules: lib.rs defines the public types (ProxyHeader, ProxiedAddress, Protocol, Tlv, Error) and the version-dispatching parse/encode entry points, while v1.rs and v2.rs contain the format-specific decode/encode logic for the text and binary PROXY protocol variants respectively, and util.rs holds small shared helpers (address-family conversion, TLV encoding primitives) used by both. The optional io module builds a ProxiedStream wrapper on top of the core parser, buffering just enough bytes to read a complete header before handing control back to the caller’s normal read/write calls — a clean layering where the wire-format logic has zero knowledge of I/O and the I/O wrapper has zero knowledge of TLV internals. Nothing in the core parser allocates or blocks, so behavior under a corrupted or truncated header (returning Error::BufferTooShort or Error::Invalid) is deterministic and easy to reason about.
Tech Stack
This is a dependency-light pure-Rust crate targeting the 2021 edition. Outside of the standard library it has exactly one optional runtime dependency pair: tokio (io-util feature) and pin-project-lite, both gated behind a tokio Cargo feature so consumers who only need synchronous parsing pay no cost. Dev-dependencies are criterion for benchmark reporting (an bench harness under benches/) and full tokio for async tests. There is no build-time codegen, no macro crate, and no unsafe FFI — io.rs uses MaybeUninit for a buffer-reuse optimization when reading, a deliberate low-level choice for a crate meant to sit on hot proxy data paths.
Code Quality
The crate has real test coverage — 21 #[test]/#[cfg(test)] markers across the ~2,000-line source tree, exercising both the v1 and v2 parse/encode round trips and TLV edge cases. CI (GitHub Actions, Rust workflow) runs cargo fmt --check, cargo clippy --all-features -- -D warnings, a full build, cargo test --all-features, and cargo doc --all-features --document-private-items on every push and PR, which is a stricter bar than many crates of this size hold themselves to. Errors are represented as a typed Error enum rather than panics or string errors, and the public API leans on Result throughout. Documentation is dense: nearly every public item carries rustdoc comments, and the module-level docs include runnable doctests that double as usage examples and regression tests.
API Design
The public surface is small and predictable: ProxyHeader::parse for decoding, encode_v1/encode_v2 (plus slice/writer variants) for encoding, and a tlvs() iterator for the optional extension fields — a caller can get a working example running from the crate-level doc comment with no additional setup. The io::ProxiedStream wrapper extends the same ergonomics to the common “peel the header off a live socket” case without requiring callers to hand-roll buffering, and gating Tokio support behind a feature flag keeps the dependency footprint minimal for synchronous users. The main friction point is the lack of a dedicated examples/ directory — usage guidance lives entirely in doc comments, which is thorough but means there’s no standalone runnable example to copy for a real project.