From: Christoph Liebender Subject: Re: relayd: add support for PROXY protocol in TCP relays To: tech@openbsd.org Date: Sun, 15 Feb 2026 22:35:58 +0100 On 2/13/26 19:58, Christoph Liebender wrote: > On 2/11/26 22:26, Stuart Henderson wrote: >>>>> diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y >>>>> diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/ >>>>> relayd.conf.5 >>>>> diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h >>>>> diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c >>>>> + * Copyright (c) 2025 Christoph Liebender >> >> copyright claims for those files seem a bit much, there's no major >> work in them >> >>>>> diff --git a/usr.sbin/relayd/proxy_protocol.c b/usr.sbin/relayd/ >>>>> proxy_protocol.c >>>>> new file mode 100644 >>>>> index 00000000000..d832d3f6678 >>>>> --- /dev/null >>>>> +++ b/usr.sbin/relayd/proxy_protocol.c >>>>> @@ -0,0 +1,142 @@ >>>>> +/* >>>>> + * Copyright (c) 2025 Christoph Liebender >> >> that one is of course reasonable >> >> (i haven't reviewed the rest of the diff) >> > > Rafael, Stuart, thanks for your comments. Updated version is attached. > > ok kirill@ ...? here goes the less nonsensical version. diff --git a/usr.sbin/relayd/Makefile b/usr.sbin/relayd/Makefile index 2e1ef449b04..5aacf84fc87 100644 --- a/usr.sbin/relayd/Makefile +++ b/usr.sbin/relayd/Makefile @@ -4,8 +4,9 @@ PROG= relayd SRCS= parse.y SRCS+= agentx_control.c ca.c carp.c check_icmp.c check_script.c \ check_tcp.c check_tls.c config.c control.c hce.c log.c \ - name2id.c pfe.c pfe_filter.c pfe_route.c proc.c relay.c \ - relay_http.c relay_udp.c relayd.c shuffle.c ssl.c util.c + name2id.c pfe.c pfe_filter.c pfe_route.c proc.c \ + proxy_protocol.c relay.c relay_http.c relay_udp.c relayd.c \ + shuffle.c ssl.c util.c MAN= relayd.8 relayd.conf.5 LDADD= -lagentx -levent -ltls -lssl -lcrypto -lutil diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y index fcdfb8e92e3..1cb07d9e53d 100644 --- a/usr.sbin/relayd/parse.y +++ b/usr.sbin/relayd/parse.y @@ -179,13 +179,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 V1 V2 %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 +1103,11 @@ proto : relay_proto PROTO STRING { } ; +optproxyproto : /* empty */ { $$ = 0; } + | PROXYPROTO V1 { $$ = F_PROXYV1; } + | PROXYPROTO V2 { $$ = F_PROXYV2; } + ; + 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,8 @@ relayoptsl : LISTEN ON STRING port opttls { rlay->rl_conf.flags |= F_TLSCLIENT; conf->sc_conf.flags |= F_TLSCLIENT; } + + rlay->rl_conf.flags |= $6; } | SESSION TIMEOUT NUMBER { if ((rlay->rl_conf.timeout.tv_sec = $3) < 0) { @@ -1983,6 +1990,7 @@ relayoptsl : LISTEN ON STRING port opttls { free($2); YYERROR; } + p->flags |= F_USED; rlay->rl_conf.proto = p->id; rlay->rl_proto = p; @@ -2479,6 +2487,7 @@ lookup(char *s) { "prefork", PREFORK }, { "priority", PRIORITY }, { "protocol", PROTO }, + { "proxy-protocol", PROXYPROTO }, { "query", QUERYSTR }, { "quick", QUICK }, { "random", RANDOM }, @@ -2518,6 +2527,8 @@ lookup(char *s) { "transparent", TRANSPARENT }, { "ttl", TTL }, { "url", URL }, + { "v1", V1 }, + { "v2", V2 }, { "value", VALUE }, { "websockets", WEBSOCKETS }, { "with", WITH } diff --git a/usr.sbin/relayd/proxy_protocol.c b/usr.sbin/relayd/proxy_protocol.c new file mode 100644 index 00000000000..ef13d7dd770 --- /dev/null +++ b/usr.sbin/relayd/proxy_protocol.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2026 Christoph Liebender + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "relayd.h" + +#define PROXY_V2_CMD_PROXY 0x01 + +#define PROXY_V2_FAM_UNSPEC 0x00 +#define PROXY_V2_FAM_TCP4 0x11 +#define PROXY_V2_FAM_UDP4 0x12 +#define PROXY_V2_FAM_TCP6 0x21 +#define PROXY_V2_FAM_UDP6 0x22 + +static const u_int8_t PROXY_V2_SIG[12] = { + 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A +}; + +struct proxy_v2_hdr { + u_int8_t sig[12]; + u_int8_t ver_cmd; + u_int8_t fam; + u_int16_t len; +}; + +union proxy_v2_addr { + struct { + u_int32_t src_addr; + u_int32_t dst_addr; + in_port_t src_port; + in_port_t dst_port; + } ipv4_addr; + struct { + u_int8_t src_addr[16]; + u_int8_t dst_addr[16]; + in_port_t src_port; + in_port_t dst_port; + } ipv6_addr; +}; + +int +proxy_protocol_v1(struct rsession *con, struct evbuffer *dstout) +{ + char ibuf[128], obuf[128]; + const char *proxyproto; + int ret; + + bzero(&ibuf, sizeof(ibuf)); + bzero(&obuf, sizeof(obuf)); + + if (print_host(&con->se_in.ss, ibuf, sizeof(ibuf)) == NULL || + print_host(&con->se_sockname, obuf, sizeof(obuf)) == NULL) + return -1; + + if (con->se_relay->rl_conf.flags & F_UDP) + proxyproto = "UNKNOWN"; + else { + switch (con->se_in.ss.ss_family) { + case AF_INET: + proxyproto = "TCP4"; + break; + case AF_INET6: + proxyproto = "TCP6"; + break; + default: + proxyproto = "UNKNOWN"; + break; + } + } + + ret = evbuffer_add_printf(dstout, + "PROXY %s %s %s %d %d\r\n", proxyproto, ibuf, obuf, + ntohs(con->se_in.port), ntohs(con->se_relay->rl_conf.port)); + + return ret == -1 ? -1 : 0; +} + +int +proxy_protocol_v2(struct rsession *con, struct evbuffer *dstout) +{ + union proxy_v2_addr addr; + struct proxy_v2_hdr hdr; + const struct relay_config *conf = &con->se_relay->rl_conf; + const struct sockaddr_storage *srcss = &con->se_in.ss; + const struct sockaddr_storage *dstss = &con->se_sockname; + int error; + in_port_t srcport = con->se_in.port; + in_port_t dstport = conf->port; + u_int16_t len; + + bcopy(PROXY_V2_SIG, hdr.sig, sizeof(hdr.sig)); + hdr.ver_cmd = 0x20 | PROXY_V2_CMD_PROXY; + + switch (dstss->ss_family) { + case AF_INET: + hdr.fam = (conf->flags & F_UDP) ? + PROXY_V2_FAM_UDP4 : PROXY_V2_FAM_TCP4; + len = sizeof(addr.ipv4_addr); + addr.ipv4_addr.src_addr = + ((const struct sockaddr_in *)srcss)->sin_addr.s_addr; + addr.ipv4_addr.dst_addr = + ((const struct sockaddr_in *)dstss)->sin_addr.s_addr; + addr.ipv4_addr.src_port = srcport; + addr.ipv4_addr.dst_port = dstport; + break; + case AF_INET6: + hdr.fam = (conf->flags & F_UDP) ? + PROXY_V2_FAM_UDP6 : PROXY_V2_FAM_TCP6; + len = sizeof(addr.ipv6_addr); + bcopy(&((const struct sockaddr_in6 *)srcss)->sin6_addr, + addr.ipv6_addr.src_addr, sizeof(addr.ipv6_addr.src_addr)); + bcopy(&((const struct sockaddr_in6 *)dstss)->sin6_addr, + addr.ipv6_addr.dst_addr, sizeof(addr.ipv6_addr.dst_addr)); + addr.ipv6_addr.src_port = srcport; + addr.ipv6_addr.dst_port = dstport; + break; + default: + hdr.fam = 0x00; + len = 0; + break; + } + + hdr.len = htons(len); + + if ((error = evbuffer_add(dstout, &hdr, sizeof(hdr))) != 0) + return error; + + if (len > 0 && (error = evbuffer_add(dstout, &addr, len)) != 0) + return error; + + return 0; +} diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c index 6d0970802c5..0a6415a6179 100644 --- a/usr.sbin/relayd/relay.c +++ b/usr.sbin/relayd/relay.c @@ -767,6 +767,19 @@ relay_connected(int fd, short sig, void *arg) "failed to allocate output buffer event", 0); return; } + + error = 0; + + if (rlay->rl_conf.flags & F_PROXYV1) + error = proxy_protocol_v1(con, bev->output); + else if (rlay->rl_conf.flags & F_PROXYV2) + error = proxy_protocol_v2(con, bev->output); + + if (error) { + relay_abort_http(con, 500, "failed to write PROXY header", 0); + return; + } + /* 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 5ae11c5ac01..bb5112c3752 100644 --- a/usr.sbin/relayd/relayd.conf.5 +++ b/usr.sbin/relayd/relayd.conf.5 @@ -673,6 +673,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 Pq Ic v1 Ns | Ns Ic v2 +Upon connection to the destination, +.Xr relayd 8 +will prepend a PROXY protocol header of the specified version 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..c4280d12e31 100644 --- a/usr.sbin/relayd/relayd.h +++ b/usr.sbin/relayd/relayd.h @@ -402,6 +402,8 @@ union hashkey { #define F_HASHKEY 0x08000000 #define F_AGENTX_TRAPONLY 0x10000000 #define F_PFLOG 0x20000000 +#define F_PROXYV1 0x40000000 +#define F_PROXYV2 0x80000000 #define F_BITS \ "\10\01DISABLE\02BACKUP\03USED\04DOWN\05ADD\06DEL\07CHANGED" \ @@ -1476,4 +1478,8 @@ int config_getrelay(struct relayd *, struct imsg *); int config_getrelaytable(struct relayd *, struct imsg *); int config_getrelayfd(struct relayd *, struct imsg *); +/* proxy_protocol.c */ +int proxy_protocol_v1(struct rsession *, struct evbuffer *); +int proxy_protocol_v2(struct rsession *, struct evbuffer *); + #endif /* RELAYD_H */