proxy-header

A lightweight Rust library for parsing and encoding HAProxy PROXY protocol v1 and v2 headers, with optional async Tokio stream support.

Library
Cargo
v0.1.2
8stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
19/100Needs Attention
Development Activity0
Maintenance20
Community8
Maturity48
Momentum0

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture80
Code Quality85
Innovation70
Learning Curve45

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::parse decoder 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 Tlvs iterator and Tlv enum, covering unique connection IDs, authority/SNI strings, and SSL/TLS metadata (client cert flags, TLS version, verification result)
  • A ProxiedStream wrapper in the io module that transparently reads and strips the header from a live stream, implemented for both blocking std::io and async Tokio I/O
  • Typed ProxiedAddress and Protocol (Stream/Datagram) structs so consuming code works with real SocketAddr values 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 TcpStream so downstream connection-handling code sees a clean stream with the proxy header already stripped and its address available via proxy_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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search