Index | Thread | Search

From:
Romain FABBRI <romain.fabbri@alienconsulting.net>
Subject:
relayd: receive the PROXY protocol (v1 and v2) on listeners
To:
"tech@openbsd.org" <tech@openbsd.org>
Date:
Mon, 13 Jul 2026 17:16:44 +0000

Download raw body.

Thread
Hi,

I am not subscribed to tech@, so please keep me in Cc on any
replies.

relayd(8) can already PREPEND a PROXY protocol header towards a
backend (proxy_protocol.c, "forward to ... proxy-protocol v1|v2").

The attached diff adds the symmetric direction: a listener can be
told to EXPECT a PROXY header from the immediately upstream proxy and
adopt the advertised client address.

Motivation:
- When relayd sits behind a TCP SNI demultiplexer that forwards the
  raw stream to a local relayd for TLS termination, every session
  appears to come from the loopback address.
- The real client address is then lost for the REMOTE_ADDR macro /
  X-Forwarded-For, for source-hash stickiness, and for logging.
- The PROXY protocol is the standard remedy; relayd could emit it but
  not consume it.

Patch design:
- one new per-listener field, relay_config.proxyproto (the F_* flag
  word is full, and a receive flag is orthogonal to the emit flags).
- the header is read before the TLS handshake via MSG_PEEK, then
  exactly its bytes are consumed with recv(2), leaving the following
  ClientHello untouched.
- a partial header reschedules on EV_READ under the existing session
  timeout (no userland reassembly).
- the version is auto-detected; a LOCAL/UNSPEC header keeps the
  accept(2) peer.
- refused together with a transparent forward (FWD_TRANS), otherwise
  the advertised address would become a bindany source (spoofing).
- as with any PROXY receiver, the listener must only be reachable
  from a trusted upstream (documented in relayd.conf(5)).

Configuration:
  listen on <addr> [tls] port <port> [proxy-protocol]

Testing (diff is against -current; built and run on both 7.9-release,
and -current, can send 7.9 patch if usefull to anyone):
- builds with no new warnings.
- "relayd -n" accepts the new keyword and rejects the FWD_TRANS
  combination , on both.
- a small unit harness around the parser (v1, v2, LOCAL, malformed
  input, exact header consumption) passes.
- verified end-to-end with sniproxy on 7.9 prepending a v2 header in front
  of a TLS-terminating relayd: the backend sees the real client
  address in X-Forwarded-For instead of the loopback address.

Feedback welcome, in particular on the naming and on whether a bare
"proxy-protocol" (auto-detected) reads well next to the versioned
emit directive.

Regards,
Romain