From: Romain FABBRI Subject: relayd: receive the PROXY protocol (v1 and v2) on listeners To: "tech@openbsd.org" Date: Mon, 13 Jul 2026 17:16:44 +0000 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 [tls] 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