Skip to content

WIP: feat(inbound): send PROXY protocol v1/v2 on configured inbound ports - #4625

Draft
daniel-garcia wants to merge 2 commits into
linkerd:mainfrom
daniel-garcia:proxy-protocol-v2-inbound-ports
Draft

daniel-garcia wants to merge 2 commits into
linkerd:mainfrom
daniel-garcia:proxy-protocol-v2-inbound-ports

Conversation

@daniel-garcia

@daniel-garcia daniel-garcia commented Sep 18, 2026 •

Copy link
Copy Markdown

Problem

Backend applications behind the inbound proxy see every opaque TCP connection as originating from the loopback, with no way to learn the real client address or the mTLS-verified client identity — the TCP counterpart of what l5d-client-id provides for HTTP. linkerd/linkerd2#15474 asks for opt-in PROXY protocol support on inbound TCP.

Solution

Two opt-in port lists (comma-separated ports/ranges, empty by default) select which inbound ports get a HAProxy PROXY protocol header, prepended on the connection the inbound proxy opens to the local application before splicing bytes:

  • LINKERD2_PROXY_INBOUND_PORTS_PROXY_PROTOCOL_V2 — binary v2 header:
    • real client address/port in the standard PP v2 address fields (works for meshed and unmeshed clients; mixed address families are encoded via the IPv6-mapped form)
    • when the connection was mutually authenticated, the verified client identity in a custom TLV (type 0xE0)
  • LINKERD2_PROXY_INBOUND_PORTS_PROXY_PROTOCOL_V1 — text v1 header (PROXY TCP4 10.1.2.3 10.9.8.7 33000 5432\r\n) for applications that only support v1. v1 has no extension mechanism, so it carries addresses only, never the identity. Mixed families are sent as TCP6 with the IPv4 address in IPv6-mapped form.
  • The two lists must be disjoint: config parsing fails, naming the port, if a port appears in both.
  • Headers are generated by the proxy from its own connection metadata — never forwarded from untrusted client input, so applications cannot receive forged addresses.

Implementation: a SendProxyProtocol connector middleware (modeled on outbound's TaggedTransport) in the shared TCP forward stack, covering both the origin-destination path and the direct transport-header path, selecting the header version per target port; TcpEndpoint now carries the client address and server-side TLS status across the forwarding boundary. Opaque/TCP forwarding only — HTTP proxying is unaffected. Config parsing mirrors ENV_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION. No new dependencies. The control-plane surface (annotations → env vars) is linkerd/linkerd2#15676.

Validation

13 new unit tests:

  • v2: byte-exact golden header encoding for IPv4/IPv6/mixed/no-identity
  • v1: exact encoding for IPv4/IPv6/mixed and the spec's 107-byte maximum
  • connect middleware: v2 write, v1 write (no identity even for an mTLS client), passthrough, per-port version selection (tokio_test::io)
  • env: v1/v2 port-set overlap detection

cargo test -p linkerd-app-inbound -p linkerd-app green; cargo clippy -p linkerd-app-inbound -p linkerd-app --all-targets clean with -D warnings. Also exercised end to end on kind with a companion linkerd2 build: an application received the expected v1 line on a v1 port and a v2 header on a v2 port, with the identity TLV only for meshed clients.

Note: the fuzzers job's linkerd/app/inbound/fuzz build fails identically on clean main (cargo-fuzz 0.13.2 needs rustc 1.91 in the rust:1.90 container, and http/fuzz.rs predates the hyper 1.x migration) — unrelated to this change.

WIP: opened for early feedback on the approach; the TLV type assignment and per-version behavior are up for discussion.

Part of linkerd/linkerd2#15474

Linkerd exposes the verified client identity to HTTP applications via
the l5d-client-id header, but has no equivalent for opaque TCP
protocols: the application sees a connection from the loopback with no
caller identity. This adds an opt-in mechanism to relay that connection
metadata on the TCP forwarding path.

When LINKERD2_PROXY_INBOUND_PORTS_PROXY_PROTOCOL_V2 (a comma-separated
port/range list, empty by default) includes the target port, the
inbound proxy prepends a HAProxy PROXY protocol v2 header to the
connection it opens to the local application, before splicing bytes.
The header carries the real client address in the standard PP v2
address fields and, when the connection was mutually authenticated, the
verified client identity in a custom TLV of type 0xE0. Mixed address
families are encoded by promoting the IPv4 address to its IPv6-mapped
form.

The header is written by a new SendProxyProtocol connector middleware
(modeled on the outbound TaggedTransport) in the shared TCP forward
stack, covering both the origin-destination path and the direct
transport-header path. TcpEndpoint now carries the client address and
server-side TLS status across the forwarding boundary to make that
metadata available at connect time. HTTP proxying is unaffected.

The control-plane configuration surface (annotation
config.linkerd.io/proxy-protocol-v2-inbound-ports rendering this
environment variable) lands separately in linkerd/linkerd2.

See linkerd/linkerd2#15637

Signed-off-by: Daniel Garcia <dgarcia@infoblox.com>
Some applications only understand the text-based PROXY protocol v1. Add
LINKERD2_PROXY_INBOUND_PORTS_PROXY_PROTOCOL_V1, a comma-separated
port/range list (empty by default), alongside the existing v2 setting.
For listed ports the inbound proxy prepends a v1 header, e.g.

    PROXY TCP4 10.1.2.3 10.9.8.7 33000 5432\r\n

to the connection it opens to the local application.

Version 1 has no extension mechanism, so the header carries the client
and server addresses only; the verified client identity is only
available through v2. As with v2, mixed address families are encoded as
TCP6 by promoting the IPv4 address to its IPv6-mapped form.

The v1 and v2 port sets must be disjoint: configuration parsing fails if
a port appears in both.

Signed-off-by: Daniel Garcia <dgarcia@infoblox.com>
@daniel-garcia daniel-garcia changed the title WIP: feat(inbound): send PROXY protocol v2 on configured inbound ports WIP: feat(inbound): send PROXY protocol v1/v2 on configured inbound ports Sep 29, 2026
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