From: Christoph Liebender Subject: relayd: add support for PROXY protocol in TCP relays To: tech@openbsd.org Date: Sat, 8 Nov 2025 20:20:17 +0100 Hi tech@, I wrote a patch to add support for the PROXY protocol (version 1) [1] to relayd(8). Works in my usecase where I have a host in a DMZ where hosts outside of the DMZ connect via a router that NATs their IP into the DMZs subnet. Essentially, what I am trying to mimic is the behavior of the proxy_protocol directive of nginx's stream proxy module [2]. I'm more than happy to hear any feedback or comments you have for me :) [1] https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt [2] https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_protocol PS: I appended my name and email to the copyright notices - also let me know if that is correct! diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y index fcdfb8e92e3..0d78cb72123 100644 --- a/usr.sbin/relayd/parse.y +++ b/usr.sbin/relayd/parse.y @@ -1,6 +1,7 @@ /* $OpenBSD: parse.y,v 1.258 2024/10/28 19:56:18 tb Exp $ */ /* + * Copyright (c) 2025 Christoph Liebender * Copyright (c) 2007 - 2014 Reyk Floeter * Copyright (c) 2008 Gilles Chehade * Copyright (c) 2006 Pierre-Yves Ritschard @@ -179,13 +180,13 @@ typedef struct { %token TIMEOUT TLS TO ROUTER RTLABEL TRANSPARENT URL WITH TTL RTABLE %token MATCH PARAMS RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD ECDHE %token EDH TICKETS CONNECTION CONNECTIONS CONTEXT ERRORS STATE CHANGES CHECKS -%token WEBSOCKETS PFLOG CLIENT +%token WEBSOCKETS PFLOG CLIENT PROXYPROTO %token STRING %token NUMBER %type context hostname interface table value path %type http_type loglevel quick %type dstmode flag forwardmode retry -%type opttls opttlsclient +%type opttls opttlsclient optproxyproto %type redirect_proto relay_proto match pflog %type action ruleaf key_option %type port @@ -1103,6 +1104,10 @@ proto : relay_proto PROTO STRING { } ; +optproxyproto : /*empty*/ { $$ = 0; } + | PROXYPROTO { $$ = 1; } + ; + protopts_n : /* empty */ | '{' '}' | '{' optnl protopts_l '}' @@ -1946,7 +1951,7 @@ relayoptsl : LISTEN ON STRING port opttls { tableport = h->port.val[0]; host_free(&al); } - | forwardmode opttlsclient TO forwardspec dstaf { + | forwardmode opttlsclient TO forwardspec dstaf optproxyproto { rlay->rl_conf.fwdmode = $1; if ($1 == FWD_ROUTE) { yyerror("no route for relays"); @@ -1956,6 +1961,9 @@ relayoptsl : LISTEN ON STRING port opttls { rlay->rl_conf.flags |= F_TLSCLIENT; conf->sc_conf.flags |= F_TLSCLIENT; } + if ($6) { + rlay->rl_conf.flags |= F_PROXYPROTO; + } } | SESSION TIMEOUT NUMBER { if ((rlay->rl_conf.timeout.tv_sec = $3) < 0) { @@ -2479,6 +2487,7 @@ lookup(char *s) { "prefork", PREFORK }, { "priority", PRIORITY }, { "protocol", PROTO }, + { "proxy-protocol", PROXYPROTO }, { "query", QUERYSTR }, { "quick", QUICK }, { "random", RANDOM }, diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c index 6d0970802c5..b8a992aa0c9 100644 --- a/usr.sbin/relayd/relay.c +++ b/usr.sbin/relayd/relay.c @@ -1,6 +1,7 @@ /* $OpenBSD: relay.c,v 1.260 2024/10/28 19:56:18 tb Exp $ */ /* + * Copyright (c) 2025 Christoph Liebender * Copyright (c) 2006 - 2014 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any @@ -682,7 +683,7 @@ relay_socket_listen(struct sockaddr_storage *ss, in_port_t port, void relay_connected(int fd, short sig, void *arg) { - char obuf[128]; + char ibuf[128], obuf[128]; struct rsession *con = arg; struct relay *rlay = con->se_relay; struct protocol *proto = rlay->rl_proto; @@ -690,6 +691,7 @@ relay_connected(int fd, short sig, void *arg) evbuffercb outwr = relay_write; struct bufferevent *bev; struct ctl_relay_event *out = &con->se_out; + const char *proxyproto; char *msg; socklen_t len; int error; @@ -767,6 +769,31 @@ relay_connected(int fd, short sig, void *arg) "failed to allocate output buffer event", 0); return; } + + if ((rlay->rl_conf.flags & F_PROXYPROTO) && + rlay->rl_proto->type == RELAY_PROTO_TCP) { + memset(&ibuf, 0, sizeof(ibuf)); + memset(&obuf, 0, sizeof(obuf)); + print_host(&con->se_in.ss, ibuf, sizeof(ibuf)); + print_host(&rlay->rl_conf.ss, obuf, sizeof(obuf)); + + switch (con->se_in.ss.ss_family) { + case AF_INET: + proxyproto = "TCP4"; + break; + case AF_INET6: + proxyproto = "TCP6"; + break; + default: + proxyproto = "UNKNOWN"; + break; + } + + evbuffer_add_printf(con->se_out.output, + "PROXY %s %s %s %d %d\r\n", proxyproto, ibuf, obuf, + ntohs(con->se_in.port), ntohs(rlay->rl_conf.port)); + } + /* write pending output buffer now */ if (bufferevent_write_buffer(bev, con->se_out.output)) { relay_abort_http(con, 500, strerror(errno), 0); diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5 index 29a13efbe8a..97f604bd0cb 100644 --- a/usr.sbin/relayd/relayd.conf.5 +++ b/usr.sbin/relayd/relayd.conf.5 @@ -1,5 +1,6 @@ .\" $OpenBSD: relayd.conf.5,v 1.213 2025/07/08 14:26:45 schwarze Exp $ .\" +.\" Copyright (c) 2025 Christoph Liebender .\" Copyright (c) 2006 - 2016 Reyk Floeter .\" Copyright (c) 2006, 2007 Pierre-Yves Ritschard .\" @@ -673,6 +674,13 @@ option will be used as a tolerance for failed host connections; the connection will be retried for .Ar number more times. +.It Ic proxy-protocol +Upon connection to the destination, +.Xr relayd 8 +will prepend a PROXY protocol header of version 1 to the relayed data, +with the IP address and port of the remote host as well as the +.Xr relayd 8 +server. .El .It Xo .Ic forward to diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h index 3b5c3987f93..4c56895f395 100644 --- a/usr.sbin/relayd/relayd.h +++ b/usr.sbin/relayd/relayd.h @@ -1,6 +1,7 @@ /* $OpenBSD: relayd.h,v 1.276 2024/10/28 19:56:18 tb Exp $ */ /* + * Copyright (c) 2025 Christoph Liebender * Copyright (c) 2006 - 2016 Reyk Floeter * Copyright (c) 2006, 2007 Pierre-Yves Ritschard * Copyright (c) 2003, 2004 Henning Brauer @@ -402,6 +403,7 @@ union hashkey { #define F_HASHKEY 0x08000000 #define F_AGENTX_TRAPONLY 0x10000000 #define F_PFLOG 0x20000000 +#define F_PROXYPROTO 0x40000000 #define F_BITS \ "\10\01DISABLE\02BACKUP\03USED\04DOWN\05ADD\06DEL\07CHANGED" \