Skip to content

feat(v2): expose application-defined TLV range (0xE0..=0xEF)#12

Merged
pigri merged 2 commits into
mainfrom
feat/custom-tlv-range
Jun 1, 2026
Merged

feat(v2): expose application-defined TLV range (0xE0..=0xEF)#12
pigri merged 2 commits into
mainfrom
feat/custom-tlv-range

Conversation

@pigri
Copy link
Copy Markdown

@pigri pigri commented Jun 1, 2026

Summary

The HAProxy PROXY protocol v2 spec § 2.2 reserves type IDs 0xE0..=0xEF for downstream consumer applications. The current ExtensionTlv enum is a closed set (Alpn, Authority, Crc32c, UniqueId, Ssl, NetNs) and the parser rejects anything outside those as InvalidTlvTypeId, so apps can't carry their own metadata through a v2 header without forking this crate.

Adds a Custom { type_id: u8, value: Vec<u8> } variant:

  • Encoder emits the stored type_id byte + raw payload.
  • Parser accepts any type_id in the documented 0xE0..=0xEF application range and surfaces it as Custom.
  • Anything outside both the builtin types and the application range still errors — the parser doesn't become a catch-all.

Use case

Synapse-proxy needs to ship per-connection JA4 fingerprints from the TLS-passthrough edge proxy to the upstream Tier-2 proxy. With TLS-passthrough the upstream can't see the ClientHello bytes, so JA4 extraction has to happen at the edge — but currently there's no in-band channel to ship the result to the upstream other than a separate Redis/event-stream. The PROXY v2 header is already crossing the same hop carrying the source address; adding a Custom TLV with the fingerprint blob is the most natural place.

Tracking issue: gen0sec/synapse#352.

Test plan

  • cargo build clean
  • cargo test — 23 tests pass (the existing 22 plus one new custom_tlv_in_app_range_round_trips that exercises encode → parse round-trip at both range boundaries 0xE0 and 0xEF)

HAProxy PROXY protocol v2 § 2.2 reserves type IDs 0xE0 through 0xEF
for downstream consumer applications. The current `ExtensionTlv` enum
is a closed set (Alpn / Authority / Crc32c / UniqueId / Ssl / NetNs)
that rejects anything outside those builtin types as
`InvalidTlvTypeId`, so apps can't ride extra metadata in the header
without forking the crate.

Add a `Custom { type_id: u8, value: Vec<u8> }` variant. Encoder
emits the stored type_id and raw byte payload; parser accepts any
type_id in `CUSTOM_TYPE_MIN..=CUSTOM_TYPE_MAX` (the spec range) and
surfaces it as Custom. Anything outside both the builtin types and
the 0xE0..=0xEF range still errors — the parser doesn't become a
catch-all.

Use case: synapse-proxy needs to ship per-connection JA4 fingerprints
from the TLS-passthrough edge to the upstream Tier-2 proxy through
the existing PROXY v2 header rather than via an out-of-band store.
See gen0sec/synapse#352.
CI's `cargo fmt -- --check` flagged the inline `Custom { type_id: u8,
value: Vec<u8> }` declaration on the v2 ExtensionTlv enum. rustfmt's
default policy breaks the struct fields onto their own lines when the
enclosing item is multi-line.
@pigri pigri merged commit 8cf34aa into main Jun 1, 2026
10 checks passed
@pigri pigri deleted the feat/custom-tlv-range branch June 1, 2026 10:55
pigri added a commit to gen0sec/pingora that referenced this pull request Jun 1, 2026
gen0sec/proxy-protocol#12 (the `ExtensionTlv::Custom` variant this
callback depends on) shipped as v0.5.3. Drop the temporary branch
reference and pin to the tagged release.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant